Overview
dynamic_webhook is not a normal Shoppex event webhook. It is a direct server-to-server callback that Shoppex sends when a DYNAMIC product is being fulfilled after a paid order.
Here’s how it works:
This page covers the callback contract for
dynamic_webhook.
For normal Shoppex event webhooks like order:paid, see Webhooks Overview and Webhook Events.When Shoppex Calls It
Shoppex calls thedynamic_webhook URL during product fulfillment after the invoice reaches a paid/completed state. The customer buys your dynamic product, Shoppex marks the invoice as paid, starts fulfillment, calls your endpoint, and saves your response into the invoice delivery details.
Request
Shoppex sends:- Method:
POST - Content-Type:
application/json - Body: JSON payload with invoice, product, shop, and line item data
Headers
| Header | Description |
|---|---|
Content-Type | Always application/json |
X-Shoppex-Idempotency-Key | Stable delivery key for deduplication |
X-Shoppex-Delivery-Id | Same value as the idempotency key |
Recommended Deduplication Rule
TreatX-Shoppex-Idempotency-Key as the durable fulfillment key. If the first request times out and Shoppex retries, both requests carry the same idempotency key — your server should return the same result instead of issuing a second token or license. This is the most common source of bugs in dynamic delivery integrations.
Payload Shape
The payload contains both camelCase and snake_case for the most important fields. This is intentional so simple handlers do not need a translation layer first.Top-Level Example
Important Fields
| Field | Type | Description |
|---|---|---|
invoiceId / invoice_id | string | Public Shoppex invoice ID |
invoiceDbId / invoice_db_id | string | Internal invoice row ID |
productId / product_id | string | Internal product row ID |
shopId / shop_id | string | Internal shop row ID |
deliveryId / delivery_id | string | Stable delivery identifier |
idempotencyKey / idempotency_key | string | Stable idempotency key |
invoice | object | Invoice snapshot |
product | object | Product snapshot |
shop | object | Shop snapshot |
line_item | object | Fulfilled line item snapshot |
Response
Your endpoint should return2xx and JSON.
Recommended response:
What Shoppex Accepts
Shoppex accepts these response forms:- A JSON object
- A JSON object with a nested
dataobject - A non-empty string
data, Shoppex stores the nested data object.
What Shoppex Stores
Shoppex normalizes your response into delivered items:dynamic_response.
If you return an empty body, Shoppex stores a fallback message and count: 0.
Retry and Timeout Behavior
Shoppex retries only transient failures:- Timeout: 15 seconds
- Retry delays: 1s, then 3s
- Retry triggers: timeout,
429,500,502,503,504, and common network errors
503, Shoppex retries after 1s. If that also fails, it retries once more after 3s. After the third attempt, the delivery is marked as failed.
URL Requirements
Yourdynamic_webhook must be a valid public http or https URL.
For local development, use a tunnel such as ngrok or Cloudflare Tunnel.
https://dev.example.com/shoppex/dynamic— workshttps://abc123.ngrok.io/shoppex/dynamic— works for local testinghttp://127.0.0.1:3000/...— won’t work, Shoppex can’t reach private/loopback URLs
Example Handler
Recommended Pattern
Here’s what a solid integration looks like:- use
idempotencyKeyas your fulfillment key - return the same result for retries
- keep the response short and structured
- put the customer-facing text in
service_text - put machine-readable output like tokens or credentials in
dynamic_response
- generating a new token on every retry
- depending on field names from only one casing style
- returning HTML or large non-JSON payloads
Next Steps
Webhooks Overview
Setup, signatures, and retry policies
Webhook Events
Full event type reference and payload schemas