Webhooks
Vindex tells your server when something happens: a customer submitted, Vindex needs more from them, a decision was made, a report is ready. Every delivery is signed, so you know it came from Vindex.
Set up an endpoint
- 1
Build a route on your server that accepts
POSTrequests with a JSON body, for examplehttps://acme.example/webhooks/vindex. - 2
In the Developers console, under Webhooks, add the URL and choose the events it should receive (or all of them). Each mode has its own endpoints.
- 3
Copy the endpoint's signing secret (
whsec_…) into your server's secrets, then press Send test event to see a delivery arrive.
https:// on the standard port (443), to a host name, never an IP address.https:// on any port. To reach your laptop while you build, use a tunnel such as ngrok.Verify the signature
Vindex signs with Standard Webhooks, so a ready-made library in your language does the checking. Each delivery has three headers:
POST /webhooks/vindex HTTP/1.1
Content-Type: application/json
webhook-id: evt_0vZ3kq9XfJ1mB7c2Lr8sTy
webhook-timestamp: 1762511719
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{"id":"evt_0vZ3kq9XfJ1mB7c2Lr8sTy","object":"event","type":"verification.decided",...}webhook-idwebhook-timestampwebhook-signaturev1, and a base64 HMAC-SHA256 of {id}.{timestamp}.{body} made with your secret. While you rotate the secret there are two, separated by a space; accept the delivery if either matches.# pip install standardwebhooks
import os
from standardwebhooks.webhooks import Webhook
webhook = Webhook(os.environ["VINDEX_WEBHOOK_SECRET"].removeprefix("whsec_"))
# Flask shown; any framework works. Read the body as raw bytes, before any JSON parsing.
@app.post("/webhooks/vindex")
def vindex_webhook():
try:
event = webhook.verify(request.get_data(), request.headers)
except Exception:
return "", 400 # wrong signature or too old: ignore it
queue.enqueue(handle_event, event) # do the work later
return "", 204 # answer quickly› Without a library
The same check by hand. Compare signatures in constant time, and use the raw body.
import base64
import hashlib
import hmac
import time
def verify(secret: str, headers, body: bytes, tolerance: int = 300) -> None:
"""Raise unless the delivery is signed with this secret and fresh."""
message_id = headers["webhook-id"]
timestamp = headers["webhook-timestamp"]
if abs(time.time() - int(timestamp)) > tolerance:
raise ValueError("timestamp outside the tolerance")
key = base64.b64decode(secret.removeprefix("whsec_"))
signed = f"{message_id}.{timestamp}.".encode() + body
expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
for signature in headers["webhook-signature"].split(" "):
version, _, value = signature.partition(",")
if version == "v1" and hmac.compare_digest(value, expected):
return
raise ValueError("no valid signature")Retries and failures
A delivery succeeds when your endpoint answers 2xx within 15 seconds. Anything else (an error, a timeout, a redirect) counts as a failure and is retried:
- 1Immediately
- 25 seconds later
- 35 minutes later
- 430 minutes later
- 52 hours later
- 65 hours later
- 710 hours later
- 810 hours later
- 924 hours later
- 1024 hours later
In live mode that is ten attempts over about three days. Waits vary by up to 20% so retries don't arrive in a burst. In test mode only the first four are made.
Redirects are not followed: give the final URL. If your endpoint answers 429 or 503 with Retry-After, Vindex waits at least that long. 410 Gone turns the endpoint off at once.
GET /v1/events (kept 30 days).Handle events safely
- Answer first, work later. Verify, queue the event, return
204. Slow handlers time out and get retried. - Expect duplicates. Delivery is at least once. Skip a
webhook-idyou have already handled. - Expect any order. Events can arrive out of order. Read the object with your key and act on its current status, not on the event alone.
- No personal data. Events are thin: ids, statuses and your references. Fetch details with your key when you need them.
- Check the mode.
livemodetells test events from live ones.
Rotate the secret
Roll secret in the console gives you a new secret. For the overlap you choose (none, an hour, or up to 24 hours) Vindex signs every delivery with both, so you can update your servers without missing anything. Libraries accept a delivery if any signature matches. Every reveal and roll is written to your account's audit log.
Event catalogue
Every event has the same envelope; data describes the object. Full examples for each type are in the API reference.
{
"id": "evt_0vZ3kq9XfJ1mB7c2Lr8sTy",
"object": "event",
"type": "verification.decided",
"livemode": true,
"created_at": "2026-11-05T10:24:51.204Z",
"data": {
"id": "ver_0vZ3kq9XfJ1mB7c2Lr8sTy",
"object": "verification",
"type": "individual",
"status": "decided",
"stage": "decided",
"outcome": "approved",
"risk_rating": "low",
"external_reference": "crm-42"
}
}Verifications
verification.createdverification.startedverification.submittedverification.action_requiredverification.resubmittedverification.decidedverification.expiredverification.canceledreport.readyScreenings
screening_batch.completedBilling
billing.credits_purchasedbilling.balance_lowbilling.credits_exhaustedbilling.credits_expiringbilling.auto_recharge_failedAccount
api_key.expiringSee every delivery
The console's webhook page shows each attempt with the status your endpoint answered, how long it took and the first part of its reply, and lets you retry one now. Calling https://api.vindexgrid.com/v1/events with your key lists what was sent.
Something missing or unclear? Signed-in users can tell us from Feedback in the app.
