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
POST.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..."
}
}
}| Field | Description |
|---|---|
id | Unique event delivery identifier starting with evt_. |
event | The event key that triggered the delivery (e.g. form.submission.created). |
created | ISO-8601 timestamp representing when the event occurred. |
apiVersion | Platform API version (currently 2026-05-01). |
workspaceId | The ID of the workspace scope. |
siteId | The ID of the site scope (can be null if workspace-scoped). |
data.object | The 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 stringt.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):
| Attempt | Timing |
|---|---|
| 1st attempt | Immediate / 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
2xximmediately 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 indatato de-duplicate. - Keep the endpoint private. Treat the URL as a secret and accept only HTTPS.