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:
1
Create the product
You create a product with
type: "DYNAMIC", set dynamic_webhook, and keep the generated dynamic webhook signing secret.2
Customer pays
A customer pays for that product.
3
Shoppex calls your endpoint
Shoppex sends
POST to your dynamic_webhook URL.4
Your server responds
Your server returns delivery data, or explicitly acknowledges that it will fulfill the line item later.
5
Shoppex stores the result
Shoppex either stores the delivered content or marks the line item as
AWAITING_FULFILLMENT.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
Signing Secret
Dynamic Product Delivery has its own signing secret on the product. It is separate from normal Shoppex event webhook secrets.- Normal event webhooks: create the endpoint in Settings -> Webhooks and use that endpoint secret.
- Dynamic product delivery: set
dynamic_webhookon the product and use the product’s dynamic webhook signing secret.
dynamic_webhook_secret to set your own secret. If you set dynamic_webhook without a secret, Shoppex generates one and returns it in dynamic_webhook_secret in that create or update response. Store it immediately.
If the product has a signing secret, Shoppex signs ${deliveryId}.${timestamp}.${rawBody} and sends the digest in X-Shoppex-Signature-V2.
Reject timestamps outside a 5-minute window.
Simple example:
Recommended Deduplication Rule
TreatX-Shoppex-Idempotency-Key as the durable fulfillment key. Shoppex makes only one automatic request, but a merchant can explicitly retry a failed delivery. Your endpoint must return the same result for the same key instead of issuing a second token, license, or account.
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
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.
Acknowledge Asynchronous Fulfillment
If your system accepts the request but cannot return the delivery content within 15 seconds, respond with200 and an explicit 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:
Retry and Timeout Behavior
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 provisioned the product before the connection failed.
After a terminal failure, Shoppex:
- marks the line item as
FAILED - notifies the merchant
- emits
order:item.delivery_failedto subscribed merchant webhook endpoints
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 if a merchant explicitly 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 the same idempotency key is received again
- 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