Skip to content

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.

When a matching event occurs, we POST a JSON body to your URL with these headers:

HeaderContents
TC-Webhook-IdUnique delivery ID — use it to deduplicate retried deliveries
TC-Webhook-EventThe event type, e.g. taxvalidation.validation.completed
TC-Webhook-TimestampUnix time (seconds) when the delivery was sent
TC-Webhook-Signaturet=<timestamp>,v1=<hex HMAC> — see below
Content-Typeapplication/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.

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…e41b

To verify:

  1. Read t from the signature header and take the raw request body (before any JSON parsing).
  2. Compute HMAC-SHA256(secret, t + "." + body) and hex-encode it.
  3. Compare it to v1 using a constant-time comparison, and reject stale timestamps to prevent replays.
Node.js
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));
}

Subscribe to exact types or a namespace wildcard ending in .* (e.g. taxvalidation.*). Currently available:

Event typeFired when
taxvalidation.validation.completedA VAT check completes (any outcome)
taxvalidation.validation.invalidAdditionally, when the outcome is INVALID
taxvalidation.validation.manual-review-requiredA 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.