Skip to main content

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 the dynamic_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_webhook on the product and use the product’s dynamic webhook signing secret.
When creating or updating 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 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:
X-Shoppex-Signature is retained as a legacy HMAC-SHA512 body-only header during migration. New dynamic delivery handlers should verify X-Shoppex-Signature-V2.
Treat X-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 return 2xx and JSON. Recommended response:

What Shoppex Accepts

Shoppex accepts these response forms:
  • A JSON object
  • A JSON object with a nested data object
  • A non-empty string
If you return a JSON object with data, Shoppex stores the nested data object.

What Shoppex Stores

Shoppex normalizes your response into delivered items:
If you return a plain string, Shoppex stores it as dynamic_response.

Acknowledge Asynchronous Fulfillment

If your system accepts the request but cannot return the delivery content within 15 seconds, respond with 200 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:
The nested form is also accepted:
Pending must be explicit. An empty 2xx response keeps its historical meaning: Shoppex treats the line item as delivered and stores a placeholder delivery note. Never use an empty body to acknowledge asynchronous work.

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_failed to subscribed merchant webhook endpoints
The merchant can fulfill the failed line item manually through the dashboard or the Developer API. The merchant can also explicitly retry the dynamic delivery, but the vendor may already have provisioned the product before the ambiguous failure. Deduplicate by X-Shoppex-Idempotency-Key before performing any side effect.

URL Requirements

Your dynamic_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 — works
  • https://abc123.ngrok.io/shoppex/dynamic — works for local testing
  • http://127.0.0.1:3000/... — won’t work, Shoppex can’t reach private/loopback URLs

Example Handler

Here’s what a solid integration looks like:
  • use idempotencyKey as 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
Avoid:
  • 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