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 and the webhook endpoint object.
The event object
Something that happened in your account. Sent to your webhook endpoints and kept in the events log for 30 days.
Attributes
- idstring
Unique identifier of the event, prefixed with
evt_. Treat it as an opaque string: store it and compare it, never parse it. - objectstring
Names the type of this object. Always
event. - typeEventType
What happened. See the webhooks section for each event and its payload.
Possible valuesorder.createdorder.paidorder.assignedorder.in_productionorder.shippedorder.completedorder.on_holdorder.rejectedorder.returnedorder.cancelledcheckout.expiredqc_ticket.needs_your_actionqc_ticket.resolvedproduct.approvedproduct.rejectedstock_batch.purchasedstock_batch.completedstock.lowquote.readyquote.failedquote.approvedquote.rejectedapproval_request.approvedapproval_request.changes_requested - created_attimestamp
Point in time, ISO 8601 in UTC.
- dataobject
The object the event is about, as it is now, and what changed. Each event type documents its exact shape in the
webhookssection.
{
"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/
- Credentials
- An API key with the
webhooks:readscope (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
- typearray of EventType
Only these event types. Repeat for several.
Possible valuesorder.createdorder.paidorder.assignedorder.in_productionorder.shippedorder.completedorder.on_holdorder.rejectedorder.returnedorder.cancelledcheckout.expiredqc_ticket.needs_your_actionqc_ticket.resolvedproduct.approvedproduct.rejectedstock_batch.purchasedstock_batch.completedstock.lowquote.readyquote.failedquote.approvedquote.rejectedapproval_request.approvedapproval_request.changes_requested - objectstring
Only events about this object, by its id, for example an
ord_id. - created_aftertimestamp
Only events after this time.
- created_beforetimestamp
Only events before this time.
- limitinteger
How many results per page, from 1 to 100.
- cursorstring
Where to continue from: the
next_cursorof the previous page. Omit for the first page.
Returns200 OK
A page of events.
Responses
- 200
A page of events.
- 400
The request is invalid.
details.errorslists each problem (validation_failed), or anexpand[]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 casedetails.fix_urllinks 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 theRetry-Afterheader, then retry;detailshaslimit,window_seconds,reset_atandretry_after_seconds.
Errors use the error object.
curl "https://public-api.dapi.digital/v2/events" \
-H "x-api-key: $DAPI_API_KEY"{
"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/
- Credentials
- An API key with the
webhooks:readscope (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
- idstringRequired
Unique identifier of the event, prefixed with
evt_. Treat it as an opaque string: store it and compare it, never parse it.
Returns200 OK
The event. Returns the 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 casedetails.fix_urllinks 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 theRetry-Afterheader, then retry;detailshaslimit,window_seconds,reset_atandretry_after_seconds.
Errors use the error object.
curl "https://public-api.dapi.digital/v2/events/evt_bfP0GSqKoZFrD72H5Os0rc7HV9XP6eQJu" \
-H "x-api-key: $DAPI_API_KEY"{
"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
}
}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
- idstring
Unique identifier of the webhook endpoint, prefixed with
whe_. Treat it as an opaque string: store it and compare it, never parse it. - objectstring
Names the type of this object. Always
webhook_endpoint. - urlstring
Where events are posted.
- eventsarray of EventType
The event types this endpoint receives.
Possible valuesorder.createdorder.paidorder.assignedorder.in_productionorder.shippedorder.completedorder.on_holdorder.rejectedorder.returnedorder.cancelledcheckout.expiredqc_ticket.needs_your_actionqc_ticket.resolvedproduct.approvedproduct.rejectedstock_batch.purchasedstock_batch.completedstock.lowquote.readyquote.failedquote.approvedquote.rejectedapproval_request.approvedapproval_request.changes_requested - payload_versionstring
v2sends the event envelope documented here.v1keeps the payload of the previous API, for integrations built on it.Possible valuesv1v2 - statusstring
Disabled endpoints receive nothing.
Possible valuesenableddisabled - last_deliveryobject or null
How the latest delivery went. Null before the first one.
- created_attimestamp
Point in time, ISO 8601 in UTC.
{
"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/
- Credentials
- An API key with the
webhooks:readscope (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
- limitinteger
How many results per page, from 1 to 100.
- cursorstring
Where to continue from: the
next_cursorof the previous page. Omit for the first page.
Returns200 OK
A page of endpoints.
Responses
- 200
A page of endpoints.
- 400
The request is invalid.
details.errorslists each problem (validation_failed), or anexpand[]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 casedetails.fix_urllinks 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 theRetry-Afterheader, then retry;detailshaslimit,window_seconds,reset_atandretry_after_seconds.
Errors use the error object.
curl "https://public-api.dapi.digital/v2/webhook-endpoints" \
-H "x-api-key: $DAPI_API_KEY"{
"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
}