Dynamic Delivery
Automatically deliver product content using a secure webhook integration.
Overview
Dynamic Delivery allows SellAuth to automatically deliver product content by sending a POST request to your server. Your server generates and returns the deliverables in real time (for example: license keys, credentials, or access tokens).
Stock for Dynamic Delivery products is managed manually and can be set to infinite.
How It Works
- A customer completes checkout.
- SellAuth sends a POST request to your webhook URL for each item in the invoice.
- Your server processes the request and returns the deliverables.
- SellAuth displays the returned content to the customer.
Each product item is delivered individually, even if multiple items exist in the same cart or invoice.
Webhook Request
HTTP Details
- Method:
POST - Content-Type:
application/json - Connect timeout: 5 seconds
- Request timeout: 10 seconds
- Retries: 3 total attempts
- Retry interval: 5 seconds between each attempt
- Maximum response size: 1 MB
Example Request Body
{
"event": "INVOICE.ITEM.DELIVER-DYNAMIC",
"id": 10042,
"unique_id": "8e32b8f24c4a0-0000000010042",
"status": "partially_completed",
"status_description": null,
"price": "25.00",
"paid": "25.00",
"currency": "USD",
"amount": 1,
"tax_rate": "0.00",
"coupon_discount": "0.00",
"gateway_fee": "0.00",
"gateway": "STRIPE",
"payment_method_id": 10,
"stripe_pi_id": "pi_demo_9f83ksL2",
"email": "john.doe@example.com",
"ip": "203.0.113.42",
"country_code": "US",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0",
"shop_id": 1,
"shop_customer_id": 501,
"created_at": "2026-02-01T11:30:00Z",
"completed_at": "2026-02-01T11:31:10Z",
"updated_at": "2026-02-01T11:31:15Z",
"customer": {
"id": 501,
"shop_id": 1,
"email": "john.doe@example.com"
},
"payments": [
{
"id": 9001,
"invoice_id": 10042,
"shop_id": 1,
"status": "completed",
"amount": "25.00",
"currency": "USD",
"external_id_type": "stripe_payment_intent_id",
"external_id_value": "pi_demo_9f83ksL2",
"created_at": "2026-02-01T11:31:05Z",
"updated_at": "2026-02-01T11:31:05Z"
}
],
"item": {
"id": 7001,
"invoice_id": 10042,
"product_id": 300,
"variant_id": 45,
"status": "failed",
"price": "25.00",
"quantity": 1,
"tax_inclusive": true,
"total_tax": "0.00",
"total_price": "25.00",
"custom_fields": {
"Custom Field Name": "Custom Field Value"
},
"completed_at": "2026-02-01T11:31:15Z",
"product": {
"id": 300,
"name": "Premium Digital Access",
"type": "variant"
},
"variant": {
"id": 45,
"name": "Standard License"
}
}
}Request Headers
Idempotency-Key: c47107fff54fd943db6939b9935af39a7753b7dc9a3bd227de71d032a725bb47
X-Timestamp: 1769945465
X-Signature: 9c64b5dcf02dfbe918357f4bf92731f28d7ba6988e9af3dec821840367f420fb
Content-Type: application/jsonSignature Verification
Each request is signed using HMAC-SHA256 and can be verified using the X-Signature header.
The webhook secret can be found in your dashboard:
Storefront → Configure → Miscellaneous
https://dash.sellauth.com/shop#miscellaneous
If the secret is not visible, click Regenerate to create one.
Signature Generation Logic
The signature is generated from the raw JSON body:
hash_hmac(
'sha256',
json_encode($requestData),
$webhookSecret
);import crypto from 'crypto';
const payload = JSON.stringify(req.body);
const signature = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');
if (signature !== req.headers['x-signature']) {
throw new Error('Invalid signature');
}$payload = file_get_contents('php://input');
$signature = hash_hmac('sha256', $payload, $webhookSecret);
if ($signature !== $_SERVER['HTTP_X_SIGNATURE']) {
http_response_code(401);
exit('Invalid signature');
}import hmac
import hashlib
payload = request.get_data()
signature = hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
if signature != request.headers.get("X-Signature"):
raise Exception("Invalid signature")Webhook Response
Success Response
If your server responds with HTTP 200, the item is marked as completed and the returned body is shown to the customer.
Each line in the response body is treated as a separate deliverable.
Example Success Response
res.status(200).send(
`LICENSE-KEY-ABC-123
LICENSE-KEY-DEF-456
LICENSE-KEY-GHI-789`
);Retryable Errors
SellAuth will automatically retry the request if a network error occurs (connection timeout, request timeout, etc.) or if your server responds with:
- 429 (Too Many Requests)
- 500 (Internal Server Error)
- 501 (Not Implemented)
- 502 (Bad Gateway)
- 503 (Service Unavailable)
- 504 (Gateway Timeout)
Non-Retryable Errors
If the response status is not 200, not 429, and not any of the 5xx codes listed above:
- The item is marked as failed.
- If a response body is provided, it will be displayed on the checkout page so the customer can see your error message.
Example Error Response
res.status(400).send('We are currently out of stock, please wait for restock.');Invoice Status Behavior
- If the webhook fails or times out, the invoice status will be
partially_completed - If the webhook succeeds and all items are delivered, the invoice status will be
completed - If the webhook succeeds but not all items are delivered, the invoice status will remain
partially_completed