Webhooks
Add an endpoint under Webhooks in the dashboard, or with the API using a key that has the webhooks scope. We send a POST with a JSON body for each event the endpoint subscribes to.
Events
Section titled “Events”| Event | When |
|---|---|
credits.low |
Your balance drops below 25%, then again below 5%, of your monthly allowance. |
credits.depleted |
Your balance reaches 0. Live lookups return 402 until credits arrive. |
credits.added |
Credits arrive: a new month, an upgrade, or a top-up. |
bulk.completed |
A bulk job finished. data has the job id, name, type, counts, and credits used. |
ping |
You pressed Send test event. Sent to that endpoint only. |
Each alert level is sent once on the way down. When credits arrive, the levels reset.
Payload
Section titled “Payload”{ "id": "evt_3f9c…", "type": "credits.low", "created": 1792120504, "api_version": "1", "org_id": "…", "data": { "balance": 124, "threshold_percent": 25, "monthly_credits": 500 }}Headers: VC-Signature, VC-Event-Id, VC-Event-Type, VC-Delivery-Id. A resent delivery keeps its event id, so use it to ignore duplicates.
Verify the signature
Section titled “Verify the signature”VC-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of t + "." + rawBody with the endpoint’s signing secret (whsec_…). Reject requests that don’t match or are more than 5 minutes old.
import crypto from "node:crypto";
function verify(rawBody, header, secret) { const parts = Object.fromEntries(header.split(",").map((p) => p.split("="))); if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false; const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex"); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""));}Use the raw body exactly as received; parsing and re-serializing the JSON changes it.
Delivery and retries
Section titled “Delivery and retries”- Reply with any
2xxwithin 10 seconds. Redirects aren’t followed. - Failures are retried after 1 min, 5 min, 15 min, 30 min, 1 h, 2 h, 3 h, 4 h, 6 h, and 7 h: 11 attempts over about 24 hours.
- If the last retry fails and nothing has succeeded for a day, the endpoint is turned off and owners and admins are emailed. Turn it back on in the dashboard and resend missed deliveries from its log.
- Delivery logs are kept for 30 days.
Manage endpoints with the API
Section titled “Manage endpoints with the API”curl https://api.validationcheck.net/v1/webhook-endpoints \ -H "Authorization: Bearer $VC_KEY" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com/webhooks/validationcheck", "events": ["credits.low", "credits.depleted"]}'The response includes the secret once. GET /v1/webhook-endpoints lists endpoints, POST /v1/webhook-endpoints/{id}/rotate-secret makes a new secret, and DELETE /v1/webhook-endpoints/{id} removes one. Endpoint URLs must be public https URLs. You can have up to 10.