> For the complete documentation index, see [llms.txt](https://penida.gitbook.io/big-digital-download/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://penida.gitbook.io/big-digital-download/most-asked-questions/connect-big-digital-downloads-to-crm-erp-or-app-public-api-webhooks.md).

# Connect Big Digital Downloads to CRM, ERP or App (Public API/Webhooks)

{% hint style="warning" %}
***These features are available from the\*\*\*\*&#x20;**<mark style="color:$primary;">**Growth**</mark>**&#x20;\*\*\*\*plan and above.***
{% endhint %}

If you sell digital products on Shopify, your order data, license keys, and download analytics probably need to live in more than one place. Your CRM needs to know when someone buys an ebook. Your license server needs to validate keys when customers activate your software. Your support team needs to resend download emails or reset download limits without opening the admin every time.

Big Digital Downloads' **Public API** and **Webhooks** make all of this possible. The API gives you 21 endpoints to read orders and products, manage license keys from creation to assignment, upload and delete files, create secure download links, toggle download access, resend delivery emails, and pull download statistics. Webhooks push real-time notifications to your server whenever an order is purchased, a delivery email is sent, or a file is downloaded.

{% hint style="info" %}
Both features are currently in **Beta**.
{% endhint %}

{% tabs %}
{% tab title="Docs" icon="square-info" %}
**The full interactive API reference with all schemas and response examples is at** [<mark style="color:$primary;">**https://islandcloud.co/public-api/docs**</mark>](https://islandcloud.co/public-api/docs)
{% endtab %}
{% endtabs %}

### What can you build with the Big Digital Downloads API?

Here are real integration scenarios that merchants are building right now:

**Build a license validation server for your software.** When a customer buys your desktop app or plugin, Big DD assigns a license key. Your software calls the Validate endpoint on launch to check whether the key is valid and assigned. No third-party license service needed.

**Bulk-import license keys from your key generator.** Use the batch endpoint to push up to 5,000 keys per request, tagged by product or batch name, with automatic deduplication. Your key generation pipeline runs on its own schedule, and Big DD's inventory stays in sync.

**Sync every order to HubSpot, Salesforce, or any CRM.** When a customer purchases a digital product, a webhook fires instantly with the full order data (customer name, email, products, price, financial status). Your CRM creates or updates the contact automatically with no manual data entry.

**Automate workflows with Zapier or Make without code.** Point a Big DD webhook to your Zapier or Make webhook URL. Every order or download event becomes a trigger. Send a Slack notification when someone buys, add a row to Google Sheets, trigger a drip sequence in Klaviyo, or create a task in Asana.

**Build a custom download portal.** Use the API to list a customer's orders and their download history, generate secure download links with optional limits and PDF watermarking, and manage access programmatically. Your frontend calls your backend, which calls the Big DD API.

**Resend delivery emails or reset download limits from your support dashboard.** Instead of opening the Big DD admin for every support ticket, call the API from your helpdesk tool. One endpoint resends the email (optionally to a new address), another resets the download counter.

**Feed download analytics into your BI tool.** Pull aggregated download counts, bandwidth usage, and top-downloaded files for any time window. Export to your data warehouse or Google Sheets for custom dashboards.

**Protect your PDFs with automatic watermarking.** When creating download links via API, enable PDF stamping to watermark every PDF with custom text like the customer's name or order number. This works automatically for all PDFs in the link.

### Authentication

Every API request requires a **Bearer token** generated from your Big Digital Downloads admin. Tokens have full read and write access to your shop's orders, products, license keys, and files. Treat them like passwords: do not commit them to version control or share them in public channels.

#### How to generate your API token

{% stepper %}
{% step %}
In your Shopify admin, open the **Big Digital Downloads** app.
{% endstep %}

{% step %}
Go to **Settings > Public API**.
{% endstep %}

{% step %}
Click **Generate token**.
{% endstep %}

{% step %}
Give your token a descriptive name so you can identify it later, for example "CRM sync", "License server", or "Zapier".
{% endstep %}

{% step %}
Click **Generate**.&#x20;
{% endstep %}
{% endstepper %}

<figure><img src="/files/60YNJ8lYGTHDWcNI6fKC" alt=""><figcaption></figcaption></figure>

Big Digital Downloads shows you the full token **one time only**. Copy it immediately and store it in a secure location like a password manager or your server's environment variables.

You can revoke any token at any time from the same page. Revoking a token immediately stops all API requests using it.

#### Using your token

Include the token in the `Authorization` header of every API request:

```
Authorization: Bearer YOUR_TOKEN_HERE
```

Example with curl:

```bash
curl https://islandcloud.co/public-api/v1/digital-products \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"
```

If the token is missing or invalid, the API returns `401`.

### API endpoints

Base URL: `https://islandcloud.co`

The API is organized into 7 areas: digital products, orders, license keys, files, download links, download statistics, and customer downloads.

***

#### Digital products

**List digital products**

`GET /public-api/v1/digital-products`

Returns all digital products in your shop. Use this to discover product IDs, delivery types, access modes, download limits, and linked files before making other API calls.

**Query parameters:**

`limit` (optional, 1-100): number of results per page.

`cursor` (optional): pagination cursor from a previous response.

`include_archived` (optional, true or false): include archived products. Defaults to false.

`q` (optional, max 120 characters): search by product name.

`include` (optional): pass `product` to attach the Shopify product title and image. This costs an extra GraphQL call per product, so use it only when you need it.

`sort` (optional): `-id` (default, newest first), `id` (oldest first), `created_at`, `-created_at`.

**Example request:**

```bash
curl "https://islandcloud.co/public-api/v1/digital-products?limit=10&include=product" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"
```

**Example response (shortened):**

```json
{
  "data": [
    {
      "id": "6789abcdef0123456789abcd",
      "name": "Photography Preset Pack",
      "delivering_type": "REGULAR",
      "access": "FILE",
      "download_mode": "redirect",
      "download_type": "single_file",
      "product_gid": "gid://shopify/Product/123456789",
      "variant_gids": [],
      "use_all_variants": true,
      "license_key_tag": null,
      "license_key_mode": null,
      "download_in": 30,
      "max_downloads": 5,
      "notify_when_updated": false,
      "scheduled_delivery_date": null,
      "scheduled_delivery_timezone": null,
      "is_archived": false,
      "files": [
        {
          "id": "aaa111bbb222ccc333ddd444",
          "name": "presets-v2.zip",
          "size": 15728640,
          "type": "application/zip",
          "is_encrypted": true,
          "uploaded": true
        }
      ],
      "links": [],
      "product": {
        "title": "Photography Preset Pack",
        "image": "https://cdn.shopify.com/..."
      },
      "created_at": "2025-11-01T10:00:00.000Z",
      "updated_at": "2026-05-10T08:30:00.000Z"
    }
  ],
  "pagination": {
    "has_more": false,
    "next_cursor": null
  }
}
```

**Understanding key digital product fields**

**delivering\_type** determines how files reach the customer:

`REGULAR` means all customers receive the same files. This is the most common mode for ebooks, templates, presets, music, and any product where every buyer gets the same download.

`UNIQUE` means each customer gets different files. You assign specific files to each order line via the API or the admin. Useful for personalized content, custom artwork, or one-of-a-kind assets.

`SCHEDULED` means files are delivered on a specific future date. Use this for pre-orders or timed releases where all buyers receive the files simultaneously on a set date.

**access** determines what the customer receives:

`FILE` means the customer gets downloadable files only.

`LICENSE_KEY` means the customer receives a license key only, no files.

`FILE_WITH_LICENCE_KEY` means the customer gets both files and a license key.

**max\_downloads** is the maximum number of times each customer can download the files. `0` or `null` means unlimited.

**download\_in** is the number of days the download link stays valid after purchase. `0` or `null` means no expiry.

**files** is the list of files attached to this product. Each file includes its ID, name, size, MIME type, and encryption status.

**Get a single digital product**

`GET /public-api/v1/digital-products/{id}`

Returns the full details of one product, including the same fields as the list endpoint.

**Update download limits**

`PUT /public-api/v1/digital-products/{id}/limits`

Changes the download limit and link expiry for a product. This is the only write operation on digital products. All other product configuration (name, files, delivery type) is managed from the admin.

**Body:**

```json
{
  "max_downloads": 10,
  "download_in": 60
}
```

Set `max_downloads` to `0` or `null` for unlimited. Set `download_in` to `0` or `null` for no expiry. Maximum values: 1,000,000 downloads, 100,000 days.

**Example: give customers more downloads after a product update:**

```bash
curl -X PUT "https://islandcloud.co/public-api/v1/digital-products/6789abcdef0123456789abcd/limits" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{"max_downloads": 10, "download_in": 90}'
```

***

#### Orders

**List orders**

`GET /public-api/v1/orders`

Returns digital orders for your shop. This is the endpoint you use for CRM syncs, reporting exports, and custom order dashboards.

**Query parameters:**

`limit` (optional, 1-100): results per page.

`cursor` (optional): pagination cursor.

`created_after` (optional, ISO 8601): only orders created on or after this date (inclusive).

`created_before` (optional, ISO 8601): only orders created before this date (exclusive).

`financial_status` (optional, array): `AUTHORIZED`, `EXPIRED`, `PAID`, `PARTIALLY_PAID`, `PARTIALLY_REFUNDED`, `PENDING`, `REFUNDED`, `VOIDED`.

`digital_product_id` (optional, array of IDs): filter by one or more digital products.

`customer_email` (optional): filter by exact customer email.

`q` (optional, max 120 characters): search by order name (e.g. `#1001`).

`include` (optional, array): `product` attaches Shopify product title and image to each line. `downloads` attaches the download events recorded for each line.

`sort` (optional): `-id` (default, newest first), `id`, `created_at`, `-created_at`.

**Example: get all paid orders from last month:**

```bash
curl "https://islandcloud.co/public-api/v1/orders?financial_status=PAID&created_after=2026-05-01T00:00:00Z&created_before=2026-06-01T00:00:00Z&sort=-created_at" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"
```

**Example: find all orders for a specific customer:**

```bash
curl "https://islandcloud.co/public-api/v1/orders?customer_email=jane@example.com&include=downloads" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"
```

**Example response (shortened):**

```json
{
  "data": [
    {
      "id": "abc123def456ghi789jkl012",
      "name": "#1042",
      "gid": "gid://shopify/Order/123456789",
      "currency": "USD",
      "financial_status": "PAID",
      "fulfillment_status": null,
      "risk_level": null,
      "dl_access": "enabled",
      "is_imported": false,
      "customer": {
        "id": "cust_abc123",
        "email": "jane@example.com",
        "firstname": "Jane",
        "lastname": "Smith",
        "gid": "gid://shopify/Customer/987654321",
        "locale": "en"
      },
      "lines": [
        {
          "id": "line_001",
          "delivering_type": "REGULAR",
          "quantity": 1,
          "gid": "gid://shopify/LineItem/111222333",
          "price_cents": 2900,
          "digital_product": {
            "id": "6789abcdef0123456789abcd",
            "name": "Photography Preset Pack"
          },
          "license_keys": ["PRESET-ABCD-1234-EFGH"]
        }
      ],
      "created_at": "2026-05-15T14:30:00.000Z",
      "updated_at": "2026-05-15T14:30:00.000Z"
    }
  ],
  "pagination": {
    "has_more": false,
    "next_cursor": null
  }
}
```

**Understanding key order fields**

**dl\_access** is either `enabled` or `disabled`. When disabled, the customer cannot access their download page. You can toggle this via the API.

**is\_imported** is `true` when the order was imported from another system rather than created through Shopify checkout.

**lines** is the array of line items. Each line represents one digital product in the order. It includes the quantity, price in cents, the associated digital product, any assigned license keys, and optionally the download events if you used `include=downloads`.

**price\_cents** is the price in the smallest currency unit (cents for USD/EUR, pence for GBP). Divide by 100 to get the display price.

**Get a single order**

`GET /public-api/v1/orders/{id}`

Returns the full details of one order, including lines, license keys, and optionally download events.

**Enable or disable download access**

`POST /public-api/v1/orders/{id}/access`

Toggles whether the customer can access their download page for this order. Useful for revoking access after a refund or re-enabling it after resolving a dispute.

```bash
curl -X POST "https://islandcloud.co/public-api/v1/orders/abc123def456ghi789jkl012/access" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{"access": "disabled"}'
```

**Reset the download counter**

`POST /public-api/v1/orders/{id}/reset-downloads`

Resets the download count for a specific line item back to zero. Use this when a customer has reached their download limit and needs fresh access, for example after a file update or a support request.

```bash
curl -X POST "https://islandcloud.co/public-api/v1/orders/abc123def456ghi789jkl012/reset-downloads" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{"line_id": "line_001"}'
```

**Resend the delivery email**

`POST /public-api/v1/orders/{id}/resend-email`

Resends the download email to the customer. Optionally pass a new email address to update the customer's email on the order before sending.

**Example: resend to the original email:**

```bash
curl -X POST "https://islandcloud.co/public-api/v1/orders/abc123def456ghi789jkl012/resend-email" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{}'
```

**Example: resend to a different email (updates the order):**

```bash
curl -X POST "https://islandcloud.co/public-api/v1/orders/abc123def456ghi789jkl012/resend-email" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{"customer_email": "newemail@example.com"}'
```

**Associate files to an order line (UNIQUE delivery)**

`PUT /public-api/v1/orders/{id}/lines/{lineId}/files`

Sets which files are delivered for a specific order line. This only applies to products with `delivering_type: "UNIQUE"`, where each customer gets different files.

Pass an array of file IDs (from the Files endpoints) and optionally trigger the delivery email:

```bash
curl -X PUT "https://islandcloud.co/public-api/v1/orders/abc123def456ghi789jkl012/lines/line_001/files" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{"file_ids": ["file_aaa111", "file_bbb222"], "send_email": true}'
```

Setting `send_email` to `true` sends the delivery email to the customer after associating the files. Pass an empty `file_ids` array to remove all file associations from the line.

***

#### License keys

License key management is the most powerful part of the API. You can build a complete external license validation and distribution system without any third-party service.

**List license keys**

`GET /public-api/v1/license-keys`

Returns all license keys in your inventory.

**Query parameters:**

`limit` (optional, 1-100): results per page.

`cursor` (optional): pagination cursor.

`status` (optional): `available` (not yet assigned) or `assigned` (consumed by an order).

`tag` (optional): filter by tag.

`q` (optional, max 120 characters): search by key value.

`sort` (optional): `-id` (default), `id`, `created_at`, `-created_at`.

**Example: list all available keys tagged "photoshop-plugin":**

```bash
curl "https://islandcloud.co/public-api/v1/license-keys?status=available&tag=photoshop-plugin" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"
```

**Example response (shortened):**

```json
{
  "data": [
    {
      "id": "key_abc123",
      "key": "PLUG-ABCD-1234-EFGH-5678",
      "tag": "photoshop-plugin",
      "assigned": false,
      "order_id": null,
      "order_name": null,
      "created_at": "2026-05-01T10:00:00.000Z",
      "updated_at": "2026-05-01T10:00:00.000Z"
    }
  ],
  "pagination": {
    "has_more": true,
    "next_cursor": "eyJpZCI6ImtleV9hYmMxMjMifQ=="
  }
}
```

**Validate a license key**

`GET /public-api/v1/license-keys/validate?key=YOUR_KEY`

Checks whether a key exists and whether it is assigned to an order. This is the endpoint your software calls when a customer activates their license.

**Example:**

```bash
curl "https://islandcloud.co/public-api/v1/license-keys/validate?key=PLUG-ABCD-1234-EFGH-5678" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"
```

**Response:**

```json
{
  "data": {
    "valid": true,
    "assigned": true,
    "tag": "photoshop-plugin",
    "order_name": "#1042",
    "order_id": "abc123def456ghi789jkl012"
  }
}
```

`valid` is `true` when the key exists in your inventory. `assigned` is `true` when the key is linked to an order. If both are `true`, the customer has a legitimate license.

A key that is `valid: true` but `assigned: false` means it exists but has not been purchased yet. A key that is `valid: false` does not exist in your inventory.

**Create license keys in batch**

`POST /public-api/v1/license-keys`

Batch-create up to **5,000 license keys** in a single request. You can tag them for organization and control how duplicates are handled.

**Body:**

`keys` (required, array of strings, 1-5,000): the key strings to add.

`tag` (optional, max 120 characters): a label to group the keys, for example the product name or batch identifier.

`duplicates` (optional): `skip` (default, ignore existing keys), `create` (insert duplicates anyway), `fail` (reject the entire batch if any key already exists).

**Example: import 3 keys from your generator:**

```bash
curl -X POST "https://islandcloud.co/public-api/v1/license-keys" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "keys": [
      "PLUG-AAAA-1111-BBBB-2222",
      "PLUG-CCCC-3333-DDDD-4444",
      "PLUG-EEEE-5555-FFFF-6666"
    ],
    "tag": "photoshop-plugin-v3",
    "duplicates": "skip"
  }'
```

Supports an optional `Idempotency-Key` header. If you send the same idempotency key within a time window, the API returns the same response without creating duplicates. Use this to safely retry failed requests.

**Assign a key to an order**

`POST /public-api/v1/license-keys/{id}/assign`

Links an available key to a specific order by order name. Optionally sends the delivery email to the customer.

```bash
curl -X POST "https://islandcloud.co/public-api/v1/license-keys/key_abc123/assign" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{"order_name": "#1042", "send_email": true}'
```

The key must be in `available` status. If it is already assigned, the request fails.

**Unassign a key from an order**

`POST /public-api/v1/license-keys/{id}/unassign`

Releases a key from its order, making it available again in inventory. Use this when processing a refund or when a key was assigned by mistake.

```bash
curl -X POST "https://islandcloud.co/public-api/v1/license-keys/key_abc123/unassign" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"
```

**Delete a license key**

`DELETE /public-api/v1/license-keys/{id}`

Permanently removes an unassigned key from inventory. The key must be unassigned first. If the key is currently assigned to an order, unassign it before deleting.

```bash
curl -X DELETE "https://islandcloud.co/public-api/v1/license-keys/key_abc123" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"
```

***

#### Files

**List files**

`GET /public-api/v1/files`

Returns all files in your library.

**Query parameters:**

`limit` (optional, 1-100): results per page.

`cursor` (optional): pagination cursor.

`q` (optional, max 120 characters): search by file name.

`type` (optional): filter by exact MIME type, for example `application/pdf` or `application/zip`.

`uploaded` (optional, true or false): filter by upload completion status.

`include` (optional): pass `products` to see which digital products each file is linked to.

`sort` (optional): `-id` (default), `id`, `created_at`, `-created_at`.

**Example: list all PDF files:**

```bash
curl "https://islandcloud.co/public-api/v1/files?type=application/pdf&include=products" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"
```

**Example response (shortened):**

```json
{
  "data": [
    {
      "id": "file_aaa111",
      "name": "ebook-photography-basics.pdf",
      "size": 4194304,
      "type": "application/pdf",
      "is_encrypted": true,
      "uploaded": true,
      "created_at": "2026-04-01T10:00:00.000Z",
      "updated_at": "2026-04-01T10:00:00.000Z"
    }
  ],
  "pagination": {
    "has_more": false,
    "next_cursor": null
  }
}
```

**Upload a file**

`POST /public-api/v1/files`

Uploads a file to your library. Send the file as `multipart/form-data`.

**Limits:** Maximum **25 MB** per file. Rate limited to **6 uploads per minute**.

**Example:**

```bash
curl -X POST "https://islandcloud.co/public-api/v1/files" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -F "file=@/path/to/my-template.zip"
```

{% hint style="warning" %}
**Keep your API token server-side.** For front-end uploads, proxy the request through your own backend. Never expose your token in client-side JavaScript.
{% endhint %}

**Delete a file**

`DELETE /public-api/v1/files/{id}`

Removes a file from storage and the catalog. The API refuses the request with `409 Conflict` if the file is still attached to a digital product, an order line, or a download link. Detach the file first, then delete.

```bash
curl -X DELETE "https://islandcloud.co/public-api/v1/files/file_aaa111" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"
```

***

#### Download links

**Create a secure download link**

`POST /public-api/v1/download-links`

Bundles one or more files into a shareable download URL. Optionally set a download limit and enable PDF stamping (watermarking).

**Body:**

`file_ids` (required, array, 1-100): the file IDs to include.

`use_download_limit` (optional, boolean): enable a download limit on this link.

`download_limit` (optional, integer): max number of downloads. Only applies when `use_download_limit` is `true`. `0` means unlimited.

`use_stamping` (optional, boolean): enable PDF watermarking on all PDFs in this link.

`stamping_text` (optional, max 500 characters): the text to watermark. For example the customer's name, email, or order number.

**Example: create a watermarked download link limited to 3 downloads:**

```bash
curl -X POST "https://islandcloud.co/public-api/v1/download-links" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "file_ids": ["file_aaa111", "file_bbb222"],
    "use_download_limit": true,
    "download_limit": 3,
    "use_stamping": true,
    "stamping_text": "Licensed to: jane@example.com — Order #1042"
  }'
```

{% hint style="warning" %}
**Download links can be created but not listed, edited, or deleted after creation.** Store the returned URL when you create it.
{% endhint %}

***

#### Download statistics

**Aggregated download stats**

`GET /public-api/v1/downloads/stats`

Returns download counts and bandwidth for a time window. Defaults to the last 30 days if no dates are specified.

**Query parameters:**

`start` (optional, ISO 8601): window start.

`end` (optional, ISO 8601): window end.

**Example: stats for May 2026:**

```bash
curl "https://islandcloud.co/public-api/v1/downloads/stats?start=2026-05-01T00:00:00Z&end=2026-06-01T00:00:00Z" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"
```

**Example response:**

```json
{
  "data": {
    "period": {
      "start": "2026-05-01T00:00:00.000Z",
      "end": "2026-06-01T00:00:00.000Z"
    },
    "total_downloads": 1423,
    "counted_downloads": 1380,
    "billable_downloads": 1200,
    "total_bandwidth_bytes": 2147483648,
    "top_files": [
      {
        "file": {
          "id": "file_aaa111",
          "name": "ebook-photography-basics.pdf",
          "size": 4194304,
          "type": "application/pdf"
        },
        "downloads": 342,
        "bandwidth_bytes": 1434451968
      }
    ]
  }
}
```

**total\_downloads** is every download event recorded. **counted\_downloads** excludes re-downloads. **billable\_downloads** is what counts toward your plan usage. **total\_bandwidth\_bytes** is the raw bytes served.

**Customer downloads**

`GET /public-api/v1/customers/{customer_gid}/downloads`

Returns all digital orders and recorded download events for a specific customer. Use the Shopify customer GID (e.g. `gid://shopify/Customer/987654321`) or the numeric customer ID.

```bash
curl "https://islandcloud.co/public-api/v1/customers/987654321/downloads?limit=50" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"
```

This is useful for building a customer-facing download history page or for support agents who need to see exactly what a customer downloaded and when.

#### Pagination

All list endpoints use cursor-based pagination. When the response includes `"has_more": true`, pass the `next_cursor` value as the `cursor` parameter in your next request:

```bash
curl "https://islandcloud.co/public-api/v1/orders?cursor=eyJpZCI6IjY3ODlhYmNkIn0=&limit=100" \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"
```

The sort order is preserved across pages. Maximum page size is 100 results.

#### Error codes

`200` Success.

`201` Resource created successfully (files, license keys, download links).

`400` Validation error. A required field is missing or in the wrong format.

`401` Missing or invalid token.

`403` Shop is inactive or access is restricted.

`404` Resource not found. The ID does not exist.

`409` Conflict. You are trying to delete a file that is still attached to a product, order, or download link.

`413` File too large. The upload exceeds the 25 MB limit.

`429` Rate limit exceeded. Space out your requests and retry after a moment.

### Webhooks: real-time event notifications

While the API lets you pull data on demand, webhooks push data to you instantly. Every time an order is purchased, a delivery email is sent, or a customer downloads a file, Big Digital Downloads sends a signed HTTP POST to the URL you configure.

Webhooks are essential for building real-time integrations: CRM syncs, Slack notifications, analytics pipelines, fraud detection, or any workflow that needs to react to events as they happen without polling.

#### Set up a webhook endpoint

{% stepper %}
{% step %}
In your Shopify admin, open the **Big Digital Downloads** app.
{% endstep %}

{% step %}
Go to **Settings > Webhooks**.
{% endstep %}

{% step %}
Click **Add endpoint**.
{% endstep %}

{% step %}
Enter a name for your endpoint, for example "Zapier sync", "Analytics", or "CRM pipeline".
{% endstep %}

{% step %}
Paste your **HTTPS URL**. The URL must use HTTPS.
{% endstep %}

{% step %}
Check the events you want to receive.
{% endstep %}

{% step %}
Click **Create**.
{% endstep %}
{% endstepper %}

<figure><img src="/files/OOKAHGuHjAFaRIJt6t59" alt=""><figcaption></figcaption></figure>

Big Digital Downloads displays a **signing secret** starting with `whsec_`. Copy it immediately and store it securely. This secret is shown only once and you need it to verify incoming webhook requests on your server.

You can edit any endpoint later to change the URL or enable or disable specific events. Use the **Test** button to send a test event and the **See deliveries** button to check delivery history.

#### The 4 webhook events

**digital\_order.purchased** fires when a customer completes a purchase that includes a digital product. The payload contains the full order with customer data, line items, prices, and financial status.

**digital\_order.delivered** fires when the delivery email is sent for an order. This happens automatically after purchase for REGULAR and SCHEDULED delivery products, or when you manually send or resend the email. Use this to confirm that the customer actually received their download link.

**file.downloaded** fires the first time a customer downloads a specific file from their order. Use this for analytics, to trigger onboarding emails, or to log access events in your system.

**file.redownloaded** fires when a customer downloads a file they already downloaded before. Use this to detect unusual download patterns or to track engagement with your content.

#### Verify the webhook signature

Every webhook request is signed so you can confirm it really came from Big Digital Downloads and was not tampered with. The verification process uses the same pattern as Cowlendar webhooks: HMAC-SHA256 with the signing secret, computed over `timestamp + "." + raw_body`.

Check the request headers:

`X-Webhook-Signature` contains the HMAC signature.

`X-Webhook-Timestamp` contains the Unix timestamp.

{% hint style="warning" %}
**Always compute the signature over the RAW request body**, the exact bytes the server sent, not a parsed and re-serialized JSON object. Parsing and re-serializing can change whitespace or key order, which breaks the signature.
{% endhint %}

Reject requests with a timestamp older than 5 minutes to prevent replay attacks.

**Node.js (Express):**

```js
import crypto from "crypto";
import express from "express";

const app = express();

app.post(
  "/webhooks/bigdd",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature =
      req.header("X-Webhook-Signature") || "";
    const timestamp =
      req.header("X-Webhook-Timestamp") || "";
    const rawBody = req.body.toString("utf8");

    // Reject requests older than 5 minutes
    if (
      Math.abs(Date.now() / 1000 - parseInt(timestamp, 10))
      > 300
    ) {
      return res.status(400).send("Timestamp too old");
    }

    const expected = crypto
      .createHmac(
        "sha256",
        process.env.BIGDD_WEBHOOK_SECRET
      )
      .update(`${timestamp}.${rawBody}`)
      .digest("hex");

    if (
      !crypto.timingSafeEqual(
        Buffer.from(expected),
        Buffer.from(signature)
      )
    ) {
      return res.status(401).send("Invalid signature");
    }

    const event = JSON.parse(rawBody);
    // Handle the event based on event.type
    res.status(200).send("ok");
  }
);
```

**Python (Flask):**

```python
import hmac, hashlib, os, time, json
from flask import Flask, request

app = Flask(__name__)

@app.post("/webhooks/bigdd")
def bigdd_webhook():
    signature = request.headers.get(
        "X-Webhook-Signature", ""
    )
    timestamp = request.headers.get(
        "X-Webhook-Timestamp", ""
    )
    raw_body = request.get_data(as_text=True)

    if abs(time.time() - int(timestamp)) > 300:
        return "Timestamp too old", 400

    expected = hmac.new(
        os.environ["BIGDD_WEBHOOK_SECRET"].encode(),
        f"{timestamp}.{raw_body}".encode(),
        hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(expected, signature):
        return "Invalid signature", 401

    event = json.loads(raw_body)
    # Handle the event based on event["type"]
    return "ok", 200
```

**PHP:**

```php
<?php
$secret = getenv("BIGDD_WEBHOOK_SECRET");

$rawBody   = file_get_contents("php://input");
$signature = $_SERVER["HTTP_X_WEBHOOK_SIGNATURE"] ?? "";
$timestamp = $_SERVER["HTTP_X_WEBHOOK_TIMESTAMP"] ?? "";

if (abs(time() - (int)$timestamp) > 300) {
    http_response_code(400);
    exit("Timestamp too old");
}

$expected = hash_hmac(
    "sha256",
    $timestamp . "." . $rawBody,
    $secret
);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit("Invalid signature");
}

$event = json_decode($rawBody, true);
// Handle the event based on $event["type"]
http_response_code(200);
echo "ok";
```

#### Test your webhook before going live

Each webhook endpoint has a **Test** button in the admin. Clicking it sends a test event to your URL using the same signing and headers as real events. Use this to confirm that your server can receive events, that HTTPS and firewall settings are correct, and that your signature verification code works.

After clicking Test, check the **See deliveries** button to see the HTTP status code your endpoint returned.

#### Webhook best practices

**Respond within 10 seconds.** If your handler needs to do heavy work like calling another API or writing to a database, accept the webhook immediately with `200`, then process it in a background job.

**Deduplicate events.** Store every event ID you process. If you receive the same ID again during a retry, skip it.

**Use constant-time comparison for signatures.** `crypto.timingSafeEqual` in Node.js, `hmac.compare_digest` in Python, and `hash_equals` in PHP.

### Current limitations

**Orders are created through Shopify checkout, not the API.** The API provides read access and post-purchase management (resend emails, toggle access, reset counters), but cannot create new orders.

**Digital products can only be partially updated.** You can change download limits and link expiry via API. All other product settings (name, files, delivery type, access mode) are managed from the admin.

**License keys cannot be edited after creation.** The key string itself is immutable. If you need to change a key value, delete the old one and create a new one.

**Download links cannot be listed, edited, or deleted after creation.** Store the URL when you create it.

**File uploads are limited to 25 MB and 6 per minute.** For larger files, upload them through the admin interface which supports bigger sizes.

**Files cannot be downloaded through the API.** The API manages your file library (list, upload, delete), but does not provide direct download URLs. Files are delivered to customers through order emails and download pages.

**Rate limits apply.** If you send too many requests in a short window, the API returns `429`. For bulk operations like exporting all orders, add a short delay between paginated requests, for example 200 to 500 ms.

**Webhook secrets are shown only once.** If you lose your signing secret, delete the endpoint and create a new one to get a fresh secret.

**Webhooks require HTTPS.** HTTP endpoints are not accepted.

### FAQ

<details>

<summary>Can I use the API with Zapier or Make without writing code?</summary>

Yes. For webhooks (receiving events from Big DD), create a "Custom Webhook" trigger in Zapier or Make, copy the URL they give you, and add it as an endpoint in Big DD Settings > Webhooks. For the API (sending requests to Big DD), use the "HTTP Request" action with your Bearer token to call any endpoint.

</details>

<details>

<summary>Can I build a license validation system with the API?</summary>

Yes. This is one of the strongest features. Your software calls `GET /license-keys/validate?key=XXXXX` on launch. The response tells you whether the key exists (`valid`) and whether it is assigned to an order (`assigned`). Both must be `true` for a legitimate license. No third-party license service needed.

</details>

<details>

<summary>What happens if my webhook endpoint goes down?</summary>

Big DD retries failed deliveries automatically. After multiple consecutive failures, the endpoint may be disabled. You can re-enable it from Settings > Webhooks once your server is back.

</details>

<details>

<summary>Can I have multiple webhook endpoints?</summary>

Yes. Create as many as you need, each with different event selections and URLs.

</details>

<details>

<summary>Can I revoke an API token?</summary>

Yes. Go to Settings > Public API and click the **Revoke** button next to the token. It stops working immediately.

</details>

<details>

<summary>Where is the full API reference?</summary>

The interactive API documentation with all schemas, parameters, and response examples is at <https://islandcloud.co/public-api/docs>

</details>
