Skip to main content

Overview

Dynamic Delivery allows you to programmatically handle product fulfillment by implementing a webhook endpoint that receives order information and returns delivery content. This is ideal for delivering digital products, license keys, game codes, or integrating with external fulfillment systems. When a customer purchases a product with dynamic delivery enabled, Komerza immediately sends a POST request to your configured webhook URL with order details, and your endpoint responds with the content to deliver to the customer.
Dynamic Delivery is configured per product variant in your product settings under delivery methods.

Use Cases

Digital Products

Deliver license keys, download links, or access codes in real-time

Third-Party Integration

Connect with external fulfillment systems or inventory management

Custom Logic

Implement complex delivery rules based on customer, product, or order data

Game Codes

Deliver game keys, activation codes, or in-game items automatically

How It Works

  1. Customer Purchases - A customer completes checkout for a product with dynamic delivery enabled
  2. Webhook Triggered - Komerza sends a POST request to your configured webhook URL
  3. Your Response - Your endpoint processes the request and returns the delivery content
  4. Customer Receives - The returned content is delivered to the customer as plain text

Configuration

Setting Up Dynamic Delivery

  1. Navigate to your product in the Komerza Dashboard
  2. Select the variant you want to configure
  3. Choose Dynamic Delivery as the delivery method
  4. Enter your webhook endpoint URL
  5. Generate and save your webhook secret
Keep your webhook secret secure. It’s used to verify that requests are genuinely from Komerza.

Webhook Request

Request Headers

Content-Type: application/json
Accept: application/json
User-Agent: Komerza/1.0
X-Signature: <HMAC-SHA256-SIGNATURE>

Signature Verification

All webhook requests include an X-Signature header containing an HMAC SHA256 signature. You should verify this signature to ensure the request is from Komerza. Signature Calculation:
HMAC-SHA256(secret, request_body) -> HEX encoded
const crypto = require("crypto");

function verifySignature(secret, body, signature) {
  const calculatedSignature = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex")
    .toUpperCase();

  return calculatedSignature === signature.toUpperCase();
}

// Express middleware example
app.use("/webhook", express.raw({ type: "application/json" }));

app.post("/webhook", (req, res) => {
  const signature = req.headers["x-signature"];
  const body = req.body.toString("utf8");

  if (!verifySignature(process.env.WEBHOOK_SECRET, body, signature)) {
    return res.status(401).send("Invalid signature");
  }

  // Process webhook...
});

Payload Structure

The webhook receives a JSON payload with the following structure:
{
  "storeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "customerId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "lineItemId": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
  "productId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
  "variantId": "8f7e6d5c-4b3a-2918-7654-3210fedcba98",
  "quantity": 1,
  "order": {
    "id": "5f3a8b2c-1d4e-5f6a-7b8c-9d0e1f2a3b4c",
    "totalPrice": 29.99,
    "currency": "USD",
    "customer": {
      "email": "[email protected]",
      "name": "John Doe"
    },
    "items": [
      {
        "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
        "productName": "Premium License",
        "variantName": "1 Year",
        "quantity": 1,
        "price": 29.99
      }
    ],
    "createdAt": "2025-11-27T10:30:00Z"
  }
}

Payload Fields

storeId
string (uuid)
required
The unique identifier of your store
customerId
string (uuid)
required
The unique identifier of the customer who made the purchase
lineItemId
string (uuid)
required
The unique identifier of the specific line item in the order
productId
string (uuid)
required
The unique identifier of the product being delivered
variantId
string (uuid)
required
The unique identifier of the product variant being delivered
quantity
integer
required
The quantity of items purchased for this line item
order
object
required
Complete order information including customer details, all items, and payment information. See the Order object in the API Reference for full schema details.

Webhook Response

Response Format

Your endpoint must respond with plain text (text/plain) containing the delivery content. This will be displayed to the customer exactly as returned.
HTTP/1.1 200 OK
Content-Type: text/plain

LICENSE-KEY-ABC123-XYZ789-PREMIUM
Download: https://example.com/download/abc123
Valid until: 2026-11-27

Response Requirements

Content-Type
string
required
Must be text/plain
Status Code
integer
required
Must be 200 for successful delivery. Any other status code will be treated as a failure and trigger retry logic.
Body
string
required
The actual delivery content to show the customer. Can be multi-line. Maximum recommended length is 8,192 characters.

Example Implementations

const express = require("express");
const crypto = require("crypto");
const app = express();

// Important: Use raw body parser for signature verification
app.use("/webhook", express.raw({ type: "application/json" }));

app.post("/webhook", async (req, res) => {
  const signature = req.headers["x-signature"];
  const body = req.body.toString("utf8");

  // Verify signature
  if (!verifySignature(process.env.WEBHOOK_SECRET, body, signature)) {
    return res.status(401).send("Invalid signature");
  }

  // Parse the payload
  const payload = JSON.parse(body);

  // Generate or fetch delivery content based on your logic
  const licenseKey = await generateLicenseKey(
    payload.productId,
    payload.customerId
  );
  const downloadUrl = await createDownloadLink(payload.variantId);

  // Return plain text delivery content
  res.setHeader("Content-Type", "text/plain");
  res
    .status(200)
    .send(
      `License Key: ${licenseKey}\n` +
        `Download: ${downloadUrl}\n` +
        `Valid for: 1 Year\n` +
        `Customer: ${payload.order.customer.email}`
    );
});

function verifySignature(secret, body, signature) {
  const calculatedSignature = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex")
    .toUpperCase();
  return calculatedSignature === signature.toUpperCase();
}

app.listen(3000);

Timeout and Retry Logic

Timeout

Your webhook endpoint has 20 seconds to respond. If your endpoint doesn’t respond within this timeframe, the request will be considered failed and will be retried.
For operations that take longer than 20 seconds, consider using an asynchronous pattern: 1. Immediately return a success response 2. Process the delivery in the background 3. Use the Komerza API to update the delivery status when ready

Retry Policy

Komerza implements an automatic retry mechanism with exponential backoff for failed webhook deliveries:
  • Retry Attempts: 3 automatic retries
  • Backoff Strategy: Exponential (2^retry seconds)
    • 1st retry: after 2 seconds
    • 2nd retry: after 4 seconds
    • 3rd retry: after 8 seconds

Circuit Breaker

To protect your endpoint from being overwhelmed, Komerza implements a circuit breaker:
  • Threshold: 5 consecutive failures
  • Break Duration: 30 seconds
  • Behavior: After 5 consecutive failures, requests are paused for 30 seconds before attempting again
Ensure your endpoint is highly available. Repeated failures may trigger the circuit breaker, temporarily preventing delivery webhooks from being sent.

Error Handling

Common Error Scenarios

Cause: The signature verification failedSolution:
  • Verify you’re using the correct webhook secret
  • Ensure you’re reading the raw request body (not parsed JSON)
  • Check your HMAC implementation matches the algorithm (SHA256, HEX encoded)
Cause: Your endpoint didn’t respond within 20 seconds Solution: - Optimize your delivery generation logic - Use caching for frequently accessed data - Consider asynchronous processing for complex operations
Cause: Your endpoint returned an error response Solution: - Check your application logs for errors - Implement proper error handling and logging - Validate the payload structure before processing
Cause: Too many consecutive failures (5+)Solution:
  • Check your server health and availability
  • Review error logs to identify the root cause
  • Implement health checks and monitoring
  • The circuit breaker will automatically reset after 30 seconds

Logging and Debugging

Komerza logs all webhook requests and responses for debugging purposes:
  • Request Body: Stored (truncated to 8,192 characters)
  • Response Body: Stored (truncated to 900 characters)
  • Response Code: Recorded for each attempt
  • Signature: Stored for verification
You can view these logs in your Komerza Dashboard under Webhook Execution Logs.

Testing

Test Webhook Locally

Use a tool like ngrok or cloudflared to expose your local development server:
# Start your local server
npm start  # or your framework's dev command

# In another terminal, start ngrok
ngrok http 3000

# Use the ngrok URL (e.g., https://abc123.ngrok.io/webhook) in your Komerza product configuration

Manual Testing

You can manually test your webhook endpoint by simulating a request:
# Generate signature
SECRET="your-webhook-secret"
PAYLOAD='{"storeId":"...","customerId":"...","order":{...}}'
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print toupper($2)}')

# Send test request
curl -X POST https://your-endpoint.com/webhook \
  -H "Content-Type: application/json" \
  -H "X-Signature: $SIGNATURE" \
  -d "$PAYLOAD"

Production Testing

Test in your development/staging environment first before enabling dynamic delivery in production.
  1. Create a test product with dynamic delivery enabled
  2. Point it to your staging webhook endpoint
  3. Make a test purchase (use test mode if available)
  4. Verify the delivery content is generated correctly
  5. Check webhook execution logs in your dashboard

Support