Webhooks
Webhooks deliver event notifications from your Project to an HTTPS endpoint you control — for example when a VAT validation result is ready. Endpoints are configured in the dashboard — see Projects & Services for the settings; this page covers the wire format.
Deliveries
Section titled “Deliveries”When a matching event occurs, we POST a JSON body to your URL with these headers:
| Header | Contents |
|---|---|
TC-Webhook-Id | Unique delivery ID — use it to deduplicate retried deliveries |
TC-Webhook-Event | The event type, e.g. taxvalidation.validation.completed |
TC-Webhook-Timestamp | Unix time (seconds) when the delivery was sent |
TC-Webhook-Signature | t=<timestamp>,v1=<hex HMAC> — see below |
Content-Type | application/json |
Plus any custom headers you configured on the endpoint.
Your endpoint should respond with a 2xx status within 10 seconds — anything else counts as a failed attempt.
Retries: failed deliveries are retried up to 5 attempts, after 1, 5, 15, 60, and 180 minutes. After the final failure you can trigger a manual retry from the dashboard within 72 hours. Delivery logs are kept for 90 days.
Verifying the signature
Section titled “Verifying the signature”Every delivery is signed with your endpoint’s secret (whsec_…) using HMAC-SHA256 over the string "<timestamp>.<raw body>":
TC-Webhook-Signature: t=1751791800,v1=5f3c9a…e41bTo verify:
- Read
tfrom the signature header and take the raw request body (before any JSON parsing). - Compute
HMAC-SHA256(secret, t + "." + body)and hex-encode it. - Compare it to
v1using a constant-time comparison, and reject stale timestamps to prevent replays.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, signatureHeader, secret, toleranceSeconds = 300) { const parts = Object.fromEntries(signatureHeader.split(",").map(p => p.split("="))); const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex"); const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= toleranceSeconds; return fresh && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));}Event types
Section titled “Event types”Subscribe to exact types or a namespace wildcard ending in .* (e.g. taxvalidation.*). Currently available:
| Event type | Fired when |
|---|---|
taxvalidation.validation.completed | A VAT check completes (any outcome) |
taxvalidation.validation.invalid | Additionally, when the outcome is INVALID |
taxvalidation.validation.manual-review-required | A check enters the manual review queue |
Payload schemas are documented with the product: VAT Checker webhook events. Every payload carries eventType and occurredAt (ISO-8601) plus event-specific fields.
The Test button on an endpoint sends a synthetic event of type platform.webhook-endpoint.test with a message field — use it to check connectivity and your signature code.