# Webhooks

Events sent to your endpoints, and the events log.

**Verifying a delivery.** Every delivery is a POST with three headers:

-   `X-Dapi-Webhook-Signature`: `sha256=<hex>`, the HMAC-SHA256 of `<X-Dapi-Webhook-Timestamp>.<raw body>` keyed with the endpoint secret (`whsec_...`, shown when the endpoint is created or its secret rotated).
-   `X-Dapi-Webhook-Timestamp`: the Unix time in seconds when this attempt was signed. Every retry is signed again with a new timestamp.
-   `X-Dapi-Event-Id`: the event id (`evt_...`), the same on every retry of one event.

To verify: compute the HMAC over the raw bytes you received (before parsing the JSON), compare it to the header in constant time, and refuse the request when the timestamp is more than 5 minutes away from your clock (replay tolerance). Answer with any 2xx within 10 seconds; failed deliveries are retried with backoff for 24 hours, so deduplicate on the event id.

This page covers [the event object](https://public-api.dapi.digital/docs/reference/webhooks.md#event_object) and [the webhook endpoint object](https://public-api.dapi.digital/docs/reference/webhooks.md#webhook_endpoint_object).

## Endpoints

-   [GET `/v2/events`](https://public-api.dapi.digital/docs/reference/webhooks.md#list_events)
-   [GET `/v2/events/{id}`](https://public-api.dapi.digital/docs/reference/webhooks.md#get_event)
-   [POST `/v2/webhook-endpoints`](https://public-api.dapi.digital/docs/reference/webhooks.md#create_webhook_endpoint)
-   [PATCH `/v2/webhook-endpoints/{id}`](https://public-api.dapi.digital/docs/reference/webhooks.md#update_webhook_endpoint)
-   [DELETE `/v2/webhook-endpoints/{id}`](https://public-api.dapi.digital/docs/reference/webhooks.md#delete_webhook_endpoint)
-   [POST `/v2/webhook-endpoints/{id}/rotate-secret`](https://public-api.dapi.digital/docs/reference/webhooks.md#rotate_webhook_endpoint_secret)
-   [POST `/v2/webhook-endpoints/{id}/test`](https://public-api.dapi.digital/docs/reference/webhooks.md#send_test_webhook_event)
-   [GET `/v2/webhook-endpoints/{id}/deliveries`](https://public-api.dapi.digital/docs/reference/webhooks.md#list_webhook_endpoint_deliveries)
-   [GET `/v2/events/{id}/deliveries`](https://public-api.dapi.digital/docs/reference/webhooks.md#get_event_deliveries)
-   [POST `/v2/events/{id}/resend`](https://public-api.dapi.digital/docs/reference/webhooks.md#resend_event)
-   [GET `/v2/webhook-endpoints`](https://public-api.dapi.digital/docs/reference/webhooks.md#list_webhook_endpoints)

## The event object

Something that happened in your account. Sent to your webhook endpoints and kept in the events log for 30 days.

### Attributes

-   `id` (string): Unique identifier of the event, prefixed with `evt_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `object` (string): Names the type of this object. Always `event`.
-   `type` (EventType): What happened. See the webhooks section for each event and its payload.
    
    Possible values: `order.created`, `order.paid`, `order.assigned`, `order.in_production`, `order.shipped`, `order.completed`, `order.on_hold`, `order.rejected`, `order.returned`, `order.cancelled`, `checkout.expired`, `qc_ticket.needs_your_action`, `qc_ticket.resolved`, `product.approved`, `product.rejected`, `stock_batch.purchased`, `stock_batch.completed`, `stock.low`, `quote.ready`, `quote.failed`, `quote.approved`, `quote.rejected`, `approval_request.approved`, `approval_request.changes_requested`.
    
-   `created_at` (timestamp): Point in time, ISO 8601 in UTC.
-   `data` (object): The object the event is about, as it is now, and what changed. Each event type documents its exact shape in the `webhooks` section.
    -   `object` (object): The object the event is about, as its GET endpoint returns it.
    -   `previous` (object or null): The fields that changed, with their old values.
    -   `region` (object or null): The region the event is about: set on `stock.low`, null on every other event.
        -   `id` (string): Unique identifier of the region, prefixed with `reg_`. Treat it as an opaque string: store it and compare it, never parse it.
        -   `object` (string): Names the type of this object. Always `region`.
        -   `name` (string): Display name of the region.

**The event object**

```json
{
  "id": "evt_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "event",
  "type": "order.shipped",
  "created_at": "2026-10-14T10:02:00Z",
  "data": {
    "object": {
      "id": "ord_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "order",
      "number": "PV-SCN-1701A372",
      "channel": "storefront",
      "status": "shipped",
      "customer": {
        "name": "Ana Popescu",
        "email": "ana@example.com",
        "phone": null,
        "company": null
      },
      "shipping_address": {
        "formatted": "Str. Test 1, Cluj, RO",
        "city": "Cluj",
        "country": "Romania",
        "country_code": "RO"
      },
      "charges": {
        "paid_by": "buyer",
        "items": {
          "amount": 1800,
          "currency": "usd"
        },
        "shipping": {
          "amount": 600,
          "currency": "usd"
        },
        "vat": {
          "amount": {
            "amount": 0,
            "currency": "usd"
          },
          "rate": 0,
          "included": false,
          "country": null
        },
        "sample_credit": null,
        "total": {
          "amount": 2400,
          "currency": "usd"
        }
      },
      "earnings": {
        "manufacturing": {
          "amount": 900,
          "currency": "usd"
        },
        "platform_fee": {
          "amount": 300,
          "currency": "usd"
        },
        "payment_processing": {
          "amount": 102,
          "currency": "usd"
        },
        "refunded": {
          "amount": 0,
          "currency": "usd"
        },
        "net": {
          "amount": 498,
          "currency": "usd"
        }
      },
      "shipping": {
        "tier": "small",
        "carrier": null,
        "tracking_number": null,
        "shipped_at": null
      },
      "external": null,
      "customer_notes": null,
      "qc": {
        "open_tickets": 1,
        "blocks_shipping": true
      },
      "created_at": "2026-10-09T08:39:29Z",
      "updated_at": "2026-10-09T08:40:02Z",
      "item_count": 1,
      "items": [
        {
          "id": "itm_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
          "object": "order_item",
          "product": {
            "id": "prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
            "object": "product",
            "name": "Clicker"
          },
          "variant": {
            "id": "var_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
            "object": "variant",
            "name": "Clicker Blue"
          },
          "sku": "CLICK-BLUE",
          "quantity": 1,
          "unit_price": {
            "amount": 1800,
            "currency": "usd"
          },
          "total": {
            "amount": 1800,
            "currency": "usd"
          },
          "stock_used": [],
          "custom_file": null,
          "fulfilled_from_stock": false
        }
      ],
      "refunds": []
    },
    "previous": {
      "status": "paid"
    },
    "region": null
  }
}
```

## List events

GET `/v2/events`

- **Credentials**: An API key with the `webhooks:read` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers and Integrators

- **Operation id**: `list_events`

Lists the events of the last 30 days, newest first, whether or not they were delivered. Use it to catch up after downtime or to reconcile what your endpoint received. Integrators see their own event types only (orders, checkouts, quotes and onboarding); `get_event_deliveries` shows where each event was sent.

### Query parameters

-   `type` (array of EventType): Only these event types. Repeat for several. Same shape as `EventType` above.
-   `object` (string): Only events about this object, by its id, for example an `ord_` id.
-   `created_after` (timestamp): Only events after this time.
-   `created_before` (timestamp): Only events before this time.
-   `limit` (integer): How many results per page, from 1 to 100.
    
    Range 1 to 100. Defaults to 20.
    
-   `cursor` (string): Where to continue from: the `next_cursor` of the previous page. Omit for the first page.

### Returns 200 OK

A page of events.

Response attributes (EventList):

-   `object` (string): Names the type of this object. Always `list`.
-   `data` (array of Event): The page of results.
    
    Described in [the event object](https://public-api.dapi.digital/docs/reference/webhooks.md#event_object).
    
-   `has_more` (boolean): True when more results exist after this page. Fetch them by passing `next_cursor` as `cursor`.
-   `next_cursor` (string or null): Opaque cursor for the next page, or null on the last page. Pass it back unchanged as `cursor`.

### Responses

-   `200`: A page of events.
-   `400`: The request is invalid. `details.errors` lists each problem (`validation_failed`), or an `expand[]` path is unknown (`invalid_expand`).
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl "https://public-api.dapi.digital/v2/events" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "object": "list",
  "data": [
    {
      "id": "evt_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "event",
      "type": "order.shipped",
      "created_at": "2026-10-14T10:02:00Z",
      "data": {
        "object": {
          "id": "ord_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
          "object": "order",
          "number": "PV-SCN-1701A372",
          "channel": "storefront",
          "status": "shipped",
          "customer": {
            "name": "Ana Popescu",
            "email": "ana@example.com",
            "phone": null,
            "company": null
          },
          "shipping_address": {
            "formatted": "Str. Test 1, Cluj, RO",
            "city": "Cluj",
            "country": "Romania",
            "country_code": "RO"
          },
          "charges": {
            "paid_by": "buyer",
            "items": {
              "amount": 1800,
              "currency": "usd"
            },
            "shipping": {
              "amount": 600,
              "currency": "usd"
            },
            "vat": {
              "amount": {
                "amount": 0,
                "currency": "usd"
              },
              "rate": 0,
              "included": false,
              "country": null
            },
            "sample_credit": null,
            "total": {
              "amount": 2400,
              "currency": "usd"
            }
          },
          "earnings": {
            "manufacturing": {
              "amount": 900,
              "currency": "usd"
            },
            "platform_fee": {
              "amount": 300,
              "currency": "usd"
            },
            "payment_processing": {
              "amount": 102,
              "currency": "usd"
            },
            "refunded": {
              "amount": 0,
              "currency": "usd"
            },
            "net": {
              "amount": 498,
              "currency": "usd"
            }
          },
          "shipping": {
            "tier": "small",
            "carrier": null,
            "tracking_number": null,
            "shipped_at": null
          },
          "external": null,
          "customer_notes": null,
          "qc": {
            "open_tickets": 1,
            "blocks_shipping": true
          },
          "created_at": "2026-10-09T08:39:29Z",
          "updated_at": "2026-10-09T08:40:02Z",
          "item_count": 1,
          "items": [
            {
              "id": "itm_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
              "object": "order_item",
              "product": {
                "id": "prd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
                "object": "product",
                "name": "Clicker"
              },
              "variant": {
                "id": "var_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
                "object": "variant",
                "name": "Clicker Blue"
              },
              "sku": "CLICK-BLUE",
              "quantity": 1,
              "unit_price": {
                "amount": 1800,
                "currency": "usd"
              },
              "total": {
                "amount": 1800,
                "currency": "usd"
              },
              "stock_used": [],
              "custom_file": null,
              "fulfilled_from_stock": false
            }
          ],
          "refunds": []
        },
        "previous": {
          "status": "paid"
        },
        "region": null
      }
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## Get an event

GET `/v2/events/{id}`

- **Credentials**: An API key with the `webhooks:read` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers and Integrators

- **Operation id**: `get_event`

Returns one event from the last 30 days, as it was sent. Integrators read their own event types only.

### Path parameters

-   `id` (string, required): Unique identifier of the event, prefixed with `evt_`. Treat it as an opaque string: store it and compare it, never parse it.

### Returns 200 OK

The event. Returns [the event object](https://public-api.dapi.digital/docs/reference/webhooks.md#event_object).

### Responses

-   `200`: The event.
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: No event with this id in the last 30 days (`event_not_found`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl "https://public-api.dapi.digital/v2/events/evt_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY"
```

## Create a webhook endpoint

POST `/v2/webhook-endpoints`

- **Credentials**: A signed-in dashboard session only; API keys are refused.

- **For**: Integrators

- **Operation id**: `create_webhook_endpoint`

Adds a URL that receives the events it subscribes to, signed with its own secret. The response is the only time the secret is shown.

You can have up to 10 endpoints, enabled or disabled, each with its own events.

Integrators only, and only with a signed-in dashboard session (`credential_not_allowed` with an API key).

### Request body

-   `url` (string, required): Where events are posted. A public `https://` URL; private, local and reserved addresses are refused, and redirects are not followed.
-   `events` (array of IntegratorEventType, required): The event types to send to this endpoint. At least one.
    
    Possible values: `order.created`, `order.paid`, `order.assigned`, `order.in_production`, `order.shipped`, `order.completed`, `order.on_hold`, `order.rejected`, `order.returned`, `order.cancelled`, `checkout.expired`, `quote.ready`, `quote.failed`, `quote.approved`, `quote.rejected`, `approval_request.approved`, `approval_request.changes_requested`.
    
-   `enabled` (boolean): Whether the endpoint receives events. Disabled endpoints count toward the endpoint limit too.
    
    Defaults to true.
    

### Returns 201 Created

The endpoint, with its secret shown once.

Response attributes (WebhookEndpointWithSecret):

-   `id` (string): Unique identifier of the webhook endpoint, prefixed with `whe_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `object` (string): Names the type of this object. Always `webhook_endpoint`.
-   `url` (string): Where events are posted.
-   `events` (array of EventType): The event types this endpoint receives. Same shape as `EventType` above.
-   `payload_version` (string): `v2` sends the event envelope documented here. `v1` keeps the payload of the previous API, for integrations built on it.
-   `status` (string): Disabled endpoints receive nothing.
    
    Possible values: `enabled`, `disabled`.
    
-   `last_delivery` (object or null): How the latest delivery went. Null before the first one.
    -   `succeeded` (boolean): True when your endpoint answered with a 2xx status. False when it answered anything else or could not be reached.
    -   `status_code` (integer or null): HTTP status your endpoint answered. Null when it could not be reached, and always null for the legacy endpoint (`payload_version: v1`, the webhook URL set before endpoints existed), which records only whether its latest delivery succeeded.
    -   `at` (timestamp): Point in time, ISO 8601 in UTC.
-   `created_at` (timestamp): Point in time, ISO 8601 in UTC.
-   `secret` (string): The signing secret of this endpoint (`whsec_...`). Shown only in this response: store it to verify `X-Dapi-Webhook-Signature`. Rotate it to get a new one.

### Responses

-   `201`: The endpoint, with its secret shown once.
-   `400`: The request is invalid, for example an event type integrators cannot receive (`validation_failed`).
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `409`: You already have 10 endpoints (`webhook_endpoint_limit_reached`). Delete one first.
-   `422`: The URL is not a public https address (`webhook_url_not_allowed`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X POST "https://public-api.dapi.digital/v2/webhook-endpoints" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/webhooks/dapi",
  "events": [
    "quote.ready",
    "quote.failed",
    "order.shipped"
  ]
}'
```

**Response 201**

```json
{
  "id": "whe_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "webhook_endpoint",
  "url": "https://example.com/webhooks/dapi",
  "events": [
    "quote.ready",
    "quote.failed",
    "order.shipped"
  ],
  "payload_version": "v2",
  "status": "enabled",
  "last_delivery": null,
  "created_at": "2026-10-01T12:00:00Z",
  "secret": "whsec_3f1c9a2b7d4e5f60718293a4b5c6d7e8f9012a3b4c5d6e7f8091a2b3c4d5e6f7"
}
```

## Update a webhook endpoint

PATCH `/v2/webhook-endpoints/{id}`

- **Credentials**: A signed-in dashboard session only; API keys are refused.

- **For**: Integrators

- **Operation id**: `update_webhook_endpoint`

Changes the URL, the events or whether the endpoint is enabled; fields you leave out stay as they are. The secret does not change. Integrators only, and only with a signed-in dashboard session (`credential_not_allowed` with an API key).

### Path parameters

-   `id` (string, required): Unique identifier of the webhook endpoint, prefixed with `whe_`. Treat it as an opaque string: store it and compare it, never parse it.

### Request body

-   `url` (string): Where events are posted. A public `https://` URL; private, local and reserved addresses are refused, and redirects are not followed.
-   `events` (array of IntegratorEventType): Replaces the subscribed event types. Same shape as `IntegratorEventType` above.
-   `enabled` (boolean): Turns delivery on or off. Deliveries due while an endpoint is disabled are skipped (`skipped`), not sent later.

### Returns 200 OK

The endpoint. Returns [the webhook endpoint object](https://public-api.dapi.digital/docs/reference/webhooks.md#webhook_endpoint_object).

### Responses

-   `200`: The endpoint.
-   `400`: The request is invalid. `details.errors` lists each problem (`validation_failed`), or an `expand[]` path is unknown (`invalid_expand`).
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: You have no dashboard endpoint with this id (`webhook_endpoint_not_found`). The previous API endpoint (`payload_version: v1`) is managed there.
-   `422`: The URL is not a public https address (`webhook_url_not_allowed`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X PATCH "https://public-api.dapi.digital/v2/webhook-endpoints/whe_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "enabled": false
}'
```

## Delete a webhook endpoint

DELETE `/v2/webhook-endpoints/{id}`

- **Credentials**: A signed-in dashboard session only; API keys are refused.

- **For**: Integrators

- **Operation id**: `delete_webhook_endpoint`

Deletes the endpoint and its pending deliveries. Events stay in the events log. This cannot be undone. Integrators only, and only with a signed-in dashboard session (`credential_not_allowed` with an API key).

### Path parameters

-   `id` (string, required): Unique identifier of the webhook endpoint, prefixed with `whe_`. Treat it as an opaque string: store it and compare it, never parse it.

### Returns 200 OK

The endpoint was deleted.

Response attributes (WebhookEndpointDeleted):

-   `id` (string): Unique identifier of the webhook endpoint, prefixed with `whe_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `object` (string): Names the type of this object. Always `webhook_endpoint`.
-   `deleted` (boolean): Always true: the endpoint is gone.

### Responses

-   `200`: The endpoint was deleted.
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: You have no dashboard endpoint with this id (`webhook_endpoint_not_found`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X DELETE "https://public-api.dapi.digital/v2/webhook-endpoints/whe_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "id": "whe_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "webhook_endpoint",
  "deleted": true
}
```

## Rotate an endpoint secret

POST `/v2/webhook-endpoints/{id}/rotate-secret`

- **Credentials**: A signed-in dashboard session only; API keys are refused.

- **For**: Integrators

- **Operation id**: `rotate_webhook_endpoint_secret`

Replaces the signing secret at once and returns the new one, shown only here. Deliveries from now on, retries included, are signed with it. Integrators only, and only with a signed-in dashboard session (`credential_not_allowed` with an API key).

### Path parameters

-   `id` (string, required): Unique identifier of the webhook endpoint, prefixed with `whe_`. Treat it as an opaque string: store it and compare it, never parse it.

### Returns 200 OK

The endpoint, with its new secret shown once.

Response attributes (WebhookEndpointWithSecret):

-   `id` (string): Unique identifier of the webhook endpoint, prefixed with `whe_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `object` (string): Names the type of this object. Always `webhook_endpoint`.
-   `url` (string): Where events are posted.
-   `events` (array of EventType): The event types this endpoint receives. Same shape as `EventType` above.
-   `payload_version` (string): `v2` sends the event envelope documented here. `v1` keeps the payload of the previous API, for integrations built on it.
-   `status` (string): Disabled endpoints receive nothing.
    
    Possible values: `enabled`, `disabled`.
    
-   `last_delivery` (object or null): How the latest delivery went. Null before the first one.
    -   `succeeded` (boolean): True when your endpoint answered with a 2xx status. False when it answered anything else or could not be reached.
    -   `status_code` (integer or null): HTTP status your endpoint answered. Null when it could not be reached, and always null for the legacy endpoint (`payload_version: v1`, the webhook URL set before endpoints existed), which records only whether its latest delivery succeeded.
    -   `at` (timestamp): Point in time, ISO 8601 in UTC.
-   `created_at` (timestamp): Point in time, ISO 8601 in UTC.
-   `secret` (string): The signing secret of this endpoint (`whsec_...`). Shown only in this response: store it to verify `X-Dapi-Webhook-Signature`. Rotate it to get a new one.

### Responses

-   `200`: The endpoint, with its new secret shown once.
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: You have no dashboard endpoint with this id (`webhook_endpoint_not_found`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X POST "https://public-api.dapi.digital/v2/webhook-endpoints/whe_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/rotate-secret" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "id": "whe_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "webhook_endpoint",
  "url": "https://example.com/webhooks/dapi",
  "events": [
    "order.shipped",
    "order.completed",
    "qc_ticket.needs_your_action"
  ],
  "payload_version": "v2",
  "status": "enabled",
  "last_delivery": {
    "succeeded": true,
    "status_code": 200,
    "at": "2026-10-14T10:02:01Z"
  },
  "created_at": "2026-10-01T12:00:00Z",
  "secret": "whsec_9a8b7c6d5e4f30211203f4e5d6c7b8a99a8b7c6d5e4f30211203f4e5d6c7b8a9"
}
```

## Send a test event

POST `/v2/webhook-endpoints/{id}/test`

- **Credentials**: A signed-in dashboard session only; API keys are refused.

- **For**: Integrators

- **Operation id**: `send_test_webhook_event`

Posts a signed `webhook.test` event to the endpoint now, so you can check that it is reachable and verifies signatures. The body is an event envelope whose `data.object` names the endpoint (`{ id, object }`); its id starts with `evt_test` and it is not kept in the events log, sent again or retried. Disabled endpoints can be tested too.

Only success or failure is reported.

Integrators only, and only with a signed-in dashboard session (`credential_not_allowed` with an API key).

### Path parameters

-   `id` (string, required): Unique identifier of the webhook endpoint, prefixed with `whe_`. Treat it as an opaque string: store it and compare it, never parse it.

### Returns 200 OK

How the test went.

Response attributes (WebhookTestResult):

-   `object` (string): Names the type of this object. Always `webhook_test`.
-   `ok` (boolean): True when your endpoint answered with a 2xx in time.
-   `message` (string): What happened, for a person.

### Responses

-   `200`: How the test went.
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: You have no dashboard endpoint with this id (`webhook_endpoint_not_found`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X POST "https://public-api.dapi.digital/v2/webhook-endpoints/whe_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/test" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "object": "webhook_test",
  "ok": true,
  "message": "Your endpoint acknowledged the test."
}
```

## List deliveries to an endpoint

GET `/v2/webhook-endpoints/{id}/deliveries`

- **Credentials**: An API key with the `webhooks:read` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Integrators

- **Operation id**: `list_webhook_endpoint_deliveries`

Lists what was sent to one endpoint, newest first, with every try and your endpoint's answers. Deliveries are kept 30 days. Integrators only; readable with a session or a key with `webhooks:read`.

### Path parameters

-   `id` (string, required): Unique identifier of the webhook endpoint, prefixed with `whe_`. Treat it as an opaque string: store it and compare it, never parse it.

### Query parameters

-   `limit` (integer): How many results per page, from 1 to 100.
    
    Range 1 to 100. Defaults to 20.
    
-   `cursor` (string): Where to continue from: the `next_cursor` of the previous page. Omit for the first page.

### Returns 200 OK

A page of deliveries.

Response attributes (WebhookDeliveryList):

-   `object` (string): Names the type of this object. Always `list`.
-   `data` (array of WebhookDelivery): The page of results.
    -   `id` (string): Unique identifier of the webhook delivery, prefixed with `whd_`. Treat it as an opaque string: store it and compare it, never parse it.
    -   `object` (string): Names the type of this object. Always `webhook_delivery`.
    -   `endpoint` (object): The webhook endpoint this delivery went to.
        -   `id` (string): Unique identifier of the webhook endpoint, prefixed with `whe_`. Treat it as an opaque string: store it and compare it, never parse it.
        -   `object` (string): Names the type of this object. Always `webhook_endpoint`.
        -   `url` (string): The URL the event was sent to.
    -   `event` (object or null): The event delivered. Null for deliveries of the previous API payload (`payload_version: v1`).
        -   `id` (string): Unique identifier of the event, prefixed with `evt_`. Treat it as an opaque string: store it and compare it, never parse it.
        -   `object` (string): Names the type of this object. Always `event`.
        -   `type` (string): The event type.
    -   `status` (WebhookDeliveryStatus):
        
        -   `pending`: being sent, or waiting for a retry (retries run for 24 hours).
        -   `succeeded`: your endpoint answered with a 2xx.
        -   `failed`: every try in the 24 hours failed.
        -   `skipped`: not sent because the endpoint was disabled when its turn came. Nothing was posted, so there are no attempts.
        
    -   `skipped_reason` (string or null): Why a `skipped` delivery was not sent: `endpoint_disabled`, the endpoint was disabled when its turn came. Null otherwise.
    -   `next_attempt_at` (timestamp or null): When the next try is due, while `pending`. Null otherwise.
    -   `attempts` (array of WebhookDeliveryAttempt): Every try, oldest first: when it started, how long it took and what your endpoint answered.
        -   `attempt` (integer): 1 for the first try.
            
            Range at least 1.
            
        -   `started_at` (timestamp): Point in time, ISO 8601 in UTC.
        -   `duration_ms` (integer): How long your endpoint took to answer or fail, in milliseconds. 0 when the request was not sent because the URL was refused.
            
            Range at least 0.
            
        -   `status_code` (integer or null): What your endpoint answered; null when it did not answer (timeout, connection refused).
        -   `succeeded` (boolean): True when your endpoint answered with a 2xx status.
        -   `error` (string or null): Why the attempt failed: `HTTP <status>` for a non-2xx answer, or `Timed out after <n>s`, `Could not connect` or `Target not allowed` when there was no answer. Null when it succeeded.
        -   `response_body` (string or null): The first 4 KB of your endpoint's answer, with secrets and personal data removed.
    -   `created_at` (timestamp): Point in time, ISO 8601 in UTC.
-   `has_more` (boolean): True when more results exist after this page. Fetch them by passing `next_cursor` as `cursor`.
-   `next_cursor` (string or null): Opaque cursor for the next page, or null on the last page. Pass it back unchanged as `cursor`.

### Responses

-   `200`: A page of deliveries.
-   `400`: The request is invalid. `details.errors` lists each problem (`validation_failed`), or an `expand[]` path is unknown (`invalid_expand`).
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: You have no dashboard endpoint with this id (`webhook_endpoint_not_found`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl "https://public-api.dapi.digital/v2/webhook-endpoints/whe_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/deliveries" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "object": "list",
  "data": [
    {
      "id": "whd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "webhook_delivery",
      "endpoint": {
        "id": "whe_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "webhook_endpoint",
        "url": "https://example.com/webhooks/dapi"
      },
      "event": {
        "id": "evt_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "event",
        "type": "quote.ready"
      },
      "status": "succeeded",
      "skipped_reason": null,
      "next_attempt_at": null,
      "attempts": [
        {
          "attempt": 1,
          "started_at": "2026-10-22T09:13:42Z",
          "duration_ms": 184,
          "status_code": 200,
          "succeeded": true,
          "error": null,
          "response_body": "ok"
        }
      ],
      "created_at": "2026-10-22T09:13:42Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

## Get where an event was sent

GET `/v2/events/{id}/deliveries`

- **Credentials**: An API key with the `webhooks:read` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Integrators

- **Operation id**: `get_event_deliveries`

Returns the deliveries of one event to your endpoints, with every try, and the API request that caused it. Use it to see why an endpoint did not receive an event. Integrators only; readable with a session or a key with `webhooks:read`.

### Path parameters

-   `id` (string, required): Unique identifier of the event, prefixed with `evt_`. Treat it as an opaque string: store it and compare it, never parse it.

### Returns 200 OK

The event, its cause and its deliveries.

Response attributes (EventDeliveries):

-   `object` (string): Names the type of this object. Always `event_deliveries`.
-   `event` (object): The event these deliveries carried.
    -   `id` (string): Unique identifier of the event, prefixed with `evt_`. Treat it as an opaque string: store it and compare it, never parse it.
    -   `object` (string): Names the type of this object. Always `event`.
    -   `type` (string): The event type.
    -   `created_at` (timestamp): Point in time, ISO 8601 in UTC.
-   `request` (object or null): The API request that caused the event (open it with get\_api\_request). Null when something else caused it, such as the slicer or a review.
    -   `id` (string): The `Request-Id` of the request, as on your Logs page.
    -   `object` (string): Names the type of this object. Always `api_request`.
-   `deliveries` (array of WebhookDelivery): Where the event was sent, oldest first: one delivery per subscribed endpoint, plus one per resend. Same shape as `WebhookDelivery` above.

### Responses

-   `200`: The event, its cause and its deliveries.
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: No event with this id in the last 30 days (`event_not_found`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl "https://public-api.dapi.digital/v2/events/evt_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/deliveries" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "object": "event_deliveries",
  "event": {
    "id": "evt_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
    "object": "event",
    "type": "quote.ready",
    "created_at": "2026-10-22T09:13:41Z"
  },
  "request": null,
  "deliveries": [
    {
      "id": "whd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "webhook_delivery",
      "endpoint": {
        "id": "whe_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "webhook_endpoint",
        "url": "https://example.com/webhooks/dapi"
      },
      "event": {
        "id": "evt_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
        "object": "event",
        "type": "quote.ready"
      },
      "status": "succeeded",
      "skipped_reason": null,
      "next_attempt_at": null,
      "attempts": [
        {
          "attempt": 1,
          "started_at": "2026-10-22T09:13:42Z",
          "duration_ms": 184,
          "status_code": 200,
          "succeeded": true,
          "error": null,
          "response_body": "ok"
        }
      ],
      "created_at": "2026-10-22T09:13:42Z"
    }
  ]
}
```

## Send an event again

POST `/v2/events/{id}/resend`

- **Credentials**: A signed-in dashboard session only; API keys are refused.

- **For**: Integrators

- **Operation id**: `resend_event`

Sends an event again to one of your endpoints, as a new delivery retried for 24 hours like any other. The body is the event as first sent, with the same event id, so a receiver that deduplicates on `X-Dapi-Event-Id` ignores it if it already processed the event. It is signed with the current secret.

The endpoint must be enabled and subscribed to the event type.

Integrators only, and only with a signed-in dashboard session (`credential_not_allowed` with an API key).

### Path parameters

-   `id` (string, required): Unique identifier of the event, prefixed with `evt_`. Treat it as an opaque string: store it and compare it, never parse it.

### Request body

-   `endpoint` (string, required): The endpoint to send the event to.

### Returns 201 Created

The new delivery, after its first try.

Response attributes (WebhookDelivery):

-   `id` (string): Unique identifier of the webhook delivery, prefixed with `whd_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `object` (string): Names the type of this object. Always `webhook_delivery`.
-   `endpoint` (object): The webhook endpoint this delivery went to.
    -   `id` (string): Unique identifier of the webhook endpoint, prefixed with `whe_`. Treat it as an opaque string: store it and compare it, never parse it.
    -   `object` (string): Names the type of this object. Always `webhook_endpoint`.
    -   `url` (string): The URL the event was sent to.
-   `event` (object or null): The event delivered. Null for deliveries of the previous API payload (`payload_version: v1`).
    -   `id` (string): Unique identifier of the event, prefixed with `evt_`. Treat it as an opaque string: store it and compare it, never parse it.
    -   `object` (string): Names the type of this object. Always `event`.
    -   `type` (string): The event type.
-   `status` (WebhookDeliveryStatus): Same shape as `WebhookDeliveryStatus` above.
-   `skipped_reason` (string or null): Why a `skipped` delivery was not sent: `endpoint_disabled`, the endpoint was disabled when its turn came. Null otherwise.
-   `next_attempt_at` (timestamp or null): When the next try is due, while `pending`. Null otherwise.
-   `attempts` (array of WebhookDeliveryAttempt): Every try, oldest first: when it started, how long it took and what your endpoint answered. Same shape as `WebhookDeliveryAttempt` above.
-   `created_at` (timestamp): Point in time, ISO 8601 in UTC.

### Responses

-   `201`: The new delivery, after its first try.
-   `400`: The request is invalid. `details.errors` lists each problem (`validation_failed`), or an `expand[]` path is unknown (`invalid_expand`).
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `404`: No such event in the last 30 days (`event_not_found`) or no such endpoint (`webhook_endpoint_not_found`).
-   `409`: The endpoint is disabled (`webhook_endpoint_disabled`) or does not receive this event type (`event_type_not_subscribed`).
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl -X POST "https://public-api.dapi.digital/v2/events/evt_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu/resend" \
  -H "x-api-key: $DAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "endpoint": "whe_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu"
}'
```

**Response 201**

```json
{
  "id": "whd_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "webhook_delivery",
  "endpoint": {
    "id": "whe_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
    "object": "webhook_endpoint",
    "url": "https://example.com/webhooks/dapi"
  },
  "event": {
    "id": "evt_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
    "object": "event",
    "type": "quote.ready"
  },
  "status": "succeeded",
  "skipped_reason": null,
  "next_attempt_at": null,
  "attempts": [
    {
      "attempt": 1,
      "started_at": "2026-10-22T09:13:42Z",
      "duration_ms": 184,
      "status_code": 200,
      "succeeded": true,
      "error": null,
      "response_body": "ok"
    }
  ],
  "created_at": "2026-10-22T09:13:42Z"
}
```

## The webhook endpoint object

A URL that receives the events it subscribes to. Endpoints and their secrets are managed in the dashboard; the secret is never returned by the API.

### Attributes

-   `id` (string): Unique identifier of the webhook endpoint, prefixed with `whe_`. Treat it as an opaque string: store it and compare it, never parse it.
-   `object` (string): Names the type of this object. Always `webhook_endpoint`.
-   `url` (string): Where events are posted.
-   `events` (array of EventType): The event types this endpoint receives. Same shape as `EventType` above.
-   `payload_version` (string): `v2` sends the event envelope documented here. `v1` keeps the payload of the previous API, for integrations built on it.
-   `status` (string): Disabled endpoints receive nothing.
    
    Possible values: `enabled`, `disabled`.
    
-   `last_delivery` (object or null): How the latest delivery went. Null before the first one.
    -   `succeeded` (boolean): True when your endpoint answered with a 2xx status. False when it answered anything else or could not be reached.
    -   `status_code` (integer or null): HTTP status your endpoint answered. Null when it could not be reached, and always null for the legacy endpoint (`payload_version: v1`, the webhook URL set before endpoints existed), which records only whether its latest delivery succeeded.
    -   `at` (timestamp): Point in time, ISO 8601 in UTC.
-   `created_at` (timestamp): Point in time, ISO 8601 in UTC.

**The webhook endpoint object**

```json
{
  "id": "whe_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
  "object": "webhook_endpoint",
  "url": "https://example.com/webhooks/dapi",
  "events": [
    "order.shipped",
    "order.completed",
    "qc_ticket.needs_your_action"
  ],
  "payload_version": "v2",
  "status": "disabled",
  "last_delivery": {
    "succeeded": true,
    "status_code": 200,
    "at": "2026-10-14T10:02:01Z"
  },
  "created_at": "2026-10-01T12:00:00Z"
}
```

## List webhook endpoints

GET `/v2/webhook-endpoints`

- **Credentials**: An API key with the `webhooks:read` scope (full access includes every scope). A signed-in dashboard session works too.

- **For**: Designers and Integrators

- **Operation id**: `list_webhook_endpoints`

Lists your webhook endpoints, the events each receives and how its last delivery went. Designers create and edit endpoints in the dashboard; integrators in the dashboard too, through the session-only endpoint operations.

### Query parameters

-   `limit` (integer): How many results per page, from 1 to 100.
    
    Range 1 to 100. Defaults to 20.
    
-   `cursor` (string): Where to continue from: the `next_cursor` of the previous page. Omit for the first page.

### Returns 200 OK

A page of endpoints.

Response attributes (WebhookEndpointList):

-   `object` (string): Names the type of this object. Always `list`.
-   `data` (array of WebhookEndpoint): The page of results.
    
    Described in [the webhook endpoint object](https://public-api.dapi.digital/docs/reference/webhooks.md#webhook_endpoint_object).
    
-   `has_more` (boolean): True when more results exist after this page. Fetch them by passing `next_cursor` as `cursor`.
-   `next_cursor` (string or null): Opaque cursor for the next page, or null on the last page. Pass it back unchanged as `cursor`.

### Responses

-   `200`: A page of endpoints.
-   `400`: The request is invalid. `details.errors` lists each problem (`validation_failed`), or an `expand[]` path is unknown (`invalid_expand`).
-   `401`: Credentials are missing (`missing_credentials`), the API key is unknown or inactive (`invalid_api_key`), the session expired (`invalid_session`), or both a key and a session were sent (`ambiguous_credentials`).
-   `403`: The credentials are valid but not allowed to do this: a scope is missing (`missing_scope`), the endpoint is for another kind of account (`actor_not_allowed`) or another kind of credential (`credential_not_allowed`), the account may no longer use the API (`account_blacklisted`), or the account must change something first, in which case `details.fix_url` links to the dashboard page that fixes it.
-   `429`: Too many requests (`rate_limited`): the limit is 500 per minute per API key (or signed-in user). Wait for the number of seconds in the `Retry-After` header, then retry; `details` has `limit`, `window_seconds`, `reset_at` and `retry_after_seconds`.

Errors use [the error object](https://public-api.dapi.digital/docs/guides/errors.md#error-object).

**Request**

```bash
curl "https://public-api.dapi.digital/v2/webhook-endpoints" \
  -H "x-api-key: $DAPI_API_KEY"
```

**Response 200**

```json
{
  "object": "list",
  "data": [
    {
      "id": "whe_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu",
      "object": "webhook_endpoint",
      "url": "https://example.com/webhooks/dapi",
      "events": [
        "order.shipped",
        "order.completed",
        "qc_ticket.needs_your_action"
      ],
      "payload_version": "v2",
      "status": "enabled",
      "last_delivery": {
        "succeeded": true,
        "status_code": 200,
        "at": "2026-10-14T10:02:01Z"
      },
      "created_at": "2026-10-01T12:00:00Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
```

---

3DAPI API Docs. This page on the web: https://public-api.dapi.digital/docs/reference/webhooks. Index of every page: https://public-api.dapi.digital/docs/llms.txt.
