TDCSites
Webhooks & API

Webhooks

Receive real-time HTTP notifications when events happen in your workspace — setup, signature verification, retries, and security.

Webhooks push real-time JSON payloads to your server the moment an event occurs in your workspace — a form submission, a publish, a domain verification, and more. Use them to trigger automations, sync to third-party systems, or run background jobs without polling the API.

Configure webhooks under API & Webhooks → Webhooks in the dashboard.

Webhooks require a plan that includes the API & Webhooks feature. See Feature Access. For the complete list of events you can subscribe to, see Event Types.


Creating a Webhook

Go to Webhooks & API→ Webhooks and select your workspace.
Click Add webhook.
Enter a Webhook Name (an internal label) and the Endpoint URL — the HTTPS destination on your server that will receive the POST.
Tick the events you want to subscribe to, grouped by resource.
Click Add webhook.
Copy the Signing Secret now — it is shown only once at creation and is required to verify payloads.

Your endpoint must be served over HTTPS and respond with a 2xx status quickly. Non-2xx responses and timeouts are treated as failures and retried (see Retry Behavior).


Payload Structure

Every event is delivered as a JSON POST with a consistent envelope. The fields are nested inside data.object, varying by event — see Event Types for per-event shapes.

{
  "id": "evt_12345678-abcd-1234-abcd-1234567890ab",
  "event": "form.submission.created",
  "created": "2026-06-06T12:00:00.000Z",
  "apiVersion": "2026-05-01",
  "workspaceId": "ws_abc123",
  "siteId": "site_xyz456",
  "data": {
    "object": {
      "...event-specific fields..."
    }
  }
}
FieldDescription
idUnique event delivery identifier starting with evt_.
eventThe event key that triggered the delivery (e.g. form.submission.created).
createdISO-8601 timestamp representing when the event occurred.
apiVersionPlatform API version (currently 2026-05-01).
workspaceIdThe ID of the workspace scope.
siteIdThe ID of the site scope (can be null if workspace-scoped).
data.objectThe event-specific payload object.

Verifying Signatures

Every delivery includes an X-TDC-Signature header so you can confirm it genuinely came from TDC Site Builder and was not tampered with or replayed.

X-TDC-Signature: t=1716801000,v1=sha256=<hex_hmac>
  • t — the Unix timestamp (seconds) when delivery was attempted.
  • v1 — an HMAC-SHA256 signature of the string t.raw_request_body, keyed with your webhook's Signing Secret.

To verify: recompute the HMAC over ${t}.${rawBody} using your secret, compare it to v1 with a constant-time comparison, and reject anything older than ~5 minutes to prevent replay.

import crypto from 'crypto';

function verifyWebhookSignature(rawBody, signatureHeader, secret) {
  const t = signatureHeader.match(/t=(\d+)/)?.[1];
  const receivedSig = signatureHeader.match(/v1=sha256=([a-f0-9]+)/)?.[1];
  if (!t || !receivedSig) return false;

  // Reject payloads older than 5 minutes (replay protection)
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${t}.${rawBody}`)
    .digest('hex');

  // Constant-time comparison (prevents timing attacks)
  return crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(receivedSig, 'hex'),
  );
}

Verify against the raw, unparsed request body. If your framework parses JSON before you can read the raw bytes, the recomputed signature won't match. Capture the raw body first.


Managing Secrets

Under API & Webhooks → Webhooks, click Edit next to a webhook:

  • Signing Secret — masked by default; click Reveal secret to view it.
  • Rotate secret — generates a new signing key and deprecates the old one immediately. Update your server's stored secret the moment you rotate.

Deliveries Log

Click Deliveries next to a webhook to inspect its history:

  • Each entry shows the status, HTTP response code (e.g. 200, 500), duration (ms), and the request/response payloads.
  • Replay — re-send any past delivery (useful after fixing server downtime).
  • Test — fire a sample payload to verify your endpoint is reachable and your verification logic works.

Retry Behavior

If your endpoint returns a non-2xx status or times out, delivery is retried automatically up to 3 total attempts (1 original attempt + up to 2 retries):

AttemptTiming
1st attemptImmediate / Initial try
2nd attempt (1st retry)1 minute after initial try
3rd attempt (2nd retry)5 minutes after 1st retry

If a webhook subscription fails 10 consecutive times, the system automatically disables it (isActive is set to false) to prevent unnecessary server load. You can re-enable the webhook at any time from the dashboard once the endpoint issue is resolved.


Security Recommendations

  • Always verify the signature before trusting a payload — never act on an unverified request.
  • Enforce the 5-minute timestamp window to block replayed deliveries.
  • Respond fast, process async. Acknowledge with 2xx immediately and do heavy work in a background job, so you don't trigger timeout retries.
  • Make handlers idempotent. Retries (and manual replays) mean the same event may arrive more than once — key on event + a stable ID in data to de-duplicate.
  • Keep the endpoint private. Treat the URL as a secret and accept only HTTPS.