> Documentation index: https://docs.sellhidden.com/llms.txt

# Dynamic delivery

Source: https://docs.sellhidden.com/dynamic-delivery

Set a product's delivery type to dynamic and paste your URL. On every paid order Hidden POSTs the signed order and delivers the deliveredData string you answer with. Use it to create accounts, generate keys or call your own licence server.

## How it works

A paid order reaches Hidden, Hidden asks your server, your server answers with a key, and the key is delivered to the buyer.

The request has the same format as a webhook. Your JSON answer's `deliveredData` is what the buyer receives.

```http
// Your endpoint receives the same signed payload as a webhook.
// Respond within 10 seconds with what the buyer should receive:
HTTP/1.1 200 OK
Content-Type: application/json

{
  "deliveredData": "Your licence: HDN-4K2P-88QL-9XQ1\nActivate at https://example.com/activate",
  "note": "Shown to the buyer under the item (optional)"
}

// Anything other than 2xx (or a timeout) marks the item "failed" with your response
// body as the error. You can redeliver from the order page in the dashboard.

// Your own supplier account cannot pay for this order? Answer 200 with
//   { "status": "failed", "code": "insufficient_balance", "error": "Balance too low" }
// The item then waits for you instead of failing: the buyer is told it is on its way,
// you get an alert, and Redeliver sends it once you have topped up.
```

**Try it before you sell.** In the product editor, **Send test request** POSTs a signed sample order to your address (`"test": true` in the body and an `X-Hidden-Test: 1` header). No order is created and nobody is charged. The editor then shows what a real order would do with your answer.

"test": true · X-Hidden-Test: 1

## Checking the signature

Every request carries `X-Hidden-Timestamp` (unix seconds) and `X-Hidden-Signature` (`sha256=` + HMAC-SHA256 of `${timestamp}.${rawBody}` with the product's secret from the editor). Compute the same HMAC over the raw body and compare in constant time, rejecting timestamps older than a few minutes.

Three steps: join the timestamp header, a dot and the raw body; compute HMAC-SHA256 with the product's secret; compare with X-Hidden-Signature in constant time.

**Node**

```javascript
import { createHmac, timingSafeEqual } from "node:crypto";

// Use the raw request body exactly as received (do not JSON.parse first).
export function verifyHiddenSignature(rawBody: string, headers: Headers, secret: string) {
  const header = headers.get("x-hidden-signature") ?? "";      // "sha256=<hex>"
  const timestamp = headers.get("x-hidden-timestamp") ?? "";   // unix seconds

  // Reject stale deliveries (replay protection), 5 minute tolerance.
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  const given = header.replace(/^sha256=/, "");
  if (given.length !== expected.length) return false;
  return timingSafeEqual(Buffer.from(given, "hex"), Buffer.from(expected, "hex"));
}

// Next.js route handler
export async function POST(req: Request) {
  const raw = await req.text();
  if (!verifyHiddenSignature(raw, req.headers, process.env.HIDDEN_WEBHOOK_SECRET!)) {
    return new Response("invalid signature", { status: 401 });
  }
  const event = JSON.parse(raw);
  // De-duplicate on the X-Hidden-Delivery header before acting.
  // ...handle event.data
  return new Response("ok");
}
```

**Python**

```python
import hmac, hashlib, time

def verify_hidden_signature(raw_body: bytes, headers: dict, secret: str) -> bool:
    given = headers.get("x-hidden-signature", "").removeprefix("sha256=")
    ts = headers.get("x-hidden-timestamp", "0")
    if abs(time.time() - int(ts)) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(given, expected)
```

**PHP**

```php
<?php
// Use the raw request body exactly as received (do not json_decode first).
$raw       = file_get_contents("php://input");
$given     = substr($_SERVER["HTTP_X_HIDDEN_SIGNATURE"] ?? "", strlen("sha256="));
$timestamp = $_SERVER["HTTP_X_HIDDEN_TIMESTAMP"] ?? "0";

if (abs(time() - (int) $timestamp) > 300) { http_response_code(401); exit; } // stale: replay protection

$expected = hash_hmac("sha256", $timestamp . "." . $raw, getenv("HIDDEN_SECRET"));
if (!hash_equals($expected, $given)) { http_response_code(401); exit; }

$order = json_decode($raw, true);
if (!empty($order["test"])) { /* a test from the product editor: nothing was paid */ }

header("Content-Type: application/json");
echo json_encode(["deliveredData" => "Your licence: HDN-4K2P-88QL-9XQ1"]);
```

> **Always verify the signature.** Anyone who learns your endpoint URL could POST to it. Verify the signature before provisioning anything.
