API documentation
View as Markdown

Webhooks

Webhooks allow your application to receive real-time notifications when events occur in your Demarky account.

Event types

EventDescription
lead.createdA new lead was submitted
lead.abandonedA lead was marked as abandoned
lead.status.changedA lead's status was updated
page.generation.completedA page generation job completed
page.deployedA page was deployed
product.createdA product was created
product.updatedA product was updated
product.deletedA product was deleted

Delivery contract

Deliveries are HTTP POST requests to your endpoint URL with a JSON body and these headers:

HeaderDescription
Demarky-Signaturet=<unix_seconds>,v1=<hex digest> — timestamp-bound HMAC signature (see below)
Demarky-Event-TypeThe event type (e.g. lead.created)
Demarky-Event-IdEvent occurrence ID (stable across subscriptions and retries)
Demarky-Delivery-IdUnique ID for this delivery (stable across retries of the same delivery)
Demarky-Account-IdOpaque account ID for the seller whose data changed
User-AgentDemarky-Webhook/1.0

The payload envelope:

{
  "id": "2f1c8d3e-6a4b-4c7d-8e9f-0a1b2c3d4e5f",
  "delivery_id": "4b8a1f2c-3d5e-4a6b-9c8d-7e0f1a2b3c4d",
  "type": "product.updated",
  "api_version": "v1",
  "occurred_at": "2026-07-25T14:32:18.221Z",
  "sequence": 1842,
  "livemode": true,
  "account_id": "acct_01...",
  "application_id": "app_01...",
  "installation_id": "ins_01...",
  "data": {
    "object": { "id": "123e4567-e89b-42d3-a456-426614174000", "version": 7 },
    "resource_url": "/v1/accounts/acct_01.../products/123e4567-e89b-42d3-a456-426614174000"
  }
}

Signature verification

Each delivery is signed with the server-generated endpoint secret. During rotation the header

contains multiple v1= values, and a receiver accepts the delivery when any one matches:

Demarky-Signature: t=1784990123,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

To verify:

  1. Parse t and every v1 signature from Demarky-Signature.
  2. Reject timestamps more than five minutes away from your current time.
  3. Compute HMAC-SHA256 over t + "." + raw_body, using the exact request body bytes and your webhook secret.
  4. Compare your digest to v1 using a constant-time comparison.
const crypto = require('crypto');

function verifyWebhook(rawBody, signatureHeader, secret, now = Date.now()) {
  const entries = signatureHeader.split(',').map((part) => part.trim().split('=', 2));
  const timestampText = entries.find(([name]) => name === 't')?.[1];
  const signatures = entries.filter(([name]) => name === 'v1').map(([, value]) => value);
  if (!timestampText || !/^\d+$/.test(timestampText) || signatures.length === 0) return false;

  const timestamp = Number(timestampText);
  const ageSeconds = Math.floor(now / 1000) - timestamp;
  if (!Number.isSafeInteger(timestamp) || Math.abs(ageSeconds) > 300) return false;

  const signedPayload = Buffer.concat([Buffer.from(timestampText + '.'), rawBody]);
  const expected = crypto.createHmac('sha256', secret).update(signedPayload).digest();
  return signatures.some((signature) => {
    if (!/^[a-f0-9]{64}$/i.test(signature)) return false;
    const received = Buffer.from(signature, 'hex');
    return received.length === expected.length && crypto.timingSafeEqual(received, expected);
  });
}

// Usage in an Express handler (rawBody must be the unparsed body bytes):
const isValid = verifyWebhook(
  rawBody,
  request.headers['demarky-signature'],
  'whsec_your_webhook_secret'
);

Retry and delivery semantics

Circuit breaker

Circuit state belongs to the endpoint. Ten consecutive retryable failures, or at least 80% retryable

failures in the latest 20 attempts, opens it. A successful half-open probe closes it. Delivery

failure never deactivates an installation or application.

Best practices

  1. Respond with 2xx quickly — the webhook dispatch does not expect a response body.
  2. Deduplicate by event id in your store before processing.
  3. Set up monitoring on delivery failure rates via the deliveries endpoint.
  4. Store the server-generated webhook secret securely when it is returned at creation.