SellAuth

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

  1. A customer completes checkout.
  2. SellAuth sends a POST request to your webhook URL for each item in the invoice.
  3. Your server processes the request and returns the deliverables.
  4. 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/json

Signature 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