dynamic_webhook is a direct server-to-server callback that Shoppex sends when a DYNAMIC product needs fulfillment after a paid order, separate from normal event webhooks.
This page covers the callback contract for
dynamic_webhook. For normal Shoppex event webhooks like order:paid, see Webhooks and Webhook events.How it works
- You create a product with
type: "DYNAMIC", setdynamic_webhook, and keep the generated dynamic webhook signing secret. - A customer pays for that product.
- Shoppex sends
POSTto yourdynamic_webhookURL. - Your server returns delivery data, or clearly states that it will fulfill the line item later.
- Shoppex either stores the delivered content or marks the line item as
AWAITING_FULFILLMENT.
Webhook request
HTTP details
Shoppex sends aPOST request with Content-Type: application/json and a JSON body that contains invoice, product, shop, and line item data.
Your dynamic_webhook URL must be a public http or https address. 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/...— does not work. Shoppex cannot reach private or loopback URLs
Example body
The payload contains both camelCase and snake_case forms for the most important fields. This is intentional, so simple handlers do not need a translation layer first.Request headers
Signature verification
Dynamic product delivery has its own signing secret on the product. This secret is separate from normal Shoppex event webhook secrets: normal webhooks use the endpoint secret from Settings → Webhooks, dynamic delivery uses the product’sdynamic_webhook_secret.
When you create or update a dynamic product through the Developer API, pass dynamic_webhook_secret to set your own secret. If you set dynamic_webhook without a secret, Shoppex generates one and returns it once as dynamic_webhook_secret in that create or update response. Store it immediately.
If the product has a signing secret, Shoppex signs ${deliveryId}.${timestamp}.${rawBody} with HMAC-SHA256 and sends the digest in X-Shoppex-Signature-V2. Reject timestamps outside a 5-minute window.
X-Shoppex-Idempotency-Key as the durable fulfillment key for this callback. Shoppex makes only one automatic request, but a merchant can retry a failed delivery manually. Your endpoint must return the same result for the same key instead of issuing a second token, license, or account.
Webhook response
Success
Your endpoint must return2xx status and JSON.
Recommended response:
- Use
idempotencyKeyas your fulfillment key. - Return the same result if a merchant retries a failed delivery.
- 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 when Shoppex sends the same idempotency key again.
- Depending on field names from only one casing style.
- Returning HTML or a large non-JSON payload.
data object, or a non-empty string.
If you return a JSON object with data, Shoppex stores the nested data object:
dynamic_response.
If your system accepts the request but cannot return the delivery content within 15 seconds, respond with 200 and a clear pending status. Shoppex then marks this line item as AWAITING_FULFILLMENT. Deliver the content later with POST /dev/v1/orders/{id}/items/{item_id}/fulfill.
The following example shows a complete callback exchange. The request body uses the same fields described above. The response is the asynchronous acknowledgement.
Example handler
Retryable errors
Shoppex makes one delivery attempt with a 15-second timeout. It does not retry automatically after a timeout, network error,429, or 5xx response. This avoids duplicating a side effect when Shoppex cannot know whether your server already delivered the product before the connection failed.
After this terminal failure, Shoppex:
- marks the line item as
FAILED - notifies the merchant
- sends
order:item.delivery_failedto subscribed merchant webhook endpoints
Non-retryable errors
An empty or accidentally empty2xx response has no retry path. Shoppex already considers the item delivered, so it does not call your endpoint again for that line item. Always send the pending response shown above when you cannot deliver content immediately.
Invoice status behavior
Shoppex calls thedynamic_webhook URL during product fulfillment, after the invoice reaches a paid or completed state. A 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.
Webhooks
Setup, signatures, and retry policies
Webhook events
Full event type reference and payload schemas