POST /v3/verify call or an automatic WhatsApp Inbound match. This makes webhooks especially important for WhatsApp Inbound flows, where your server is never part of the verification exchange.
Setup
Open the Integration page in the OTP.ID dashboard and save your callback URL. When a webhook is enabled for the first time, OTP.ID generates its signing secret automatically. Use Reveal in the Webhook Secret card when you need to copy the secret into your server configuration. The callback URL form may ask for your account password to confirm the URL change. Revealing or rotating the webhook secret uses your authenticated dashboard session and does not require a password, which also supports Google-only accounts that have not set one yet. To replace a compromised secret, click Rotate Secret and confirm the action. The old secret becomes invalid immediately, so update your signature verification before processing new events. The secret is masked until you explicitly reveal it and is never included in normal account responses.When the webhook fires
Theotp.verified event is triggered in two situations:
- A user submits the correct code to
POST /v3/verifyand the API returns"verified": true. - A user sends the correct WhatsApp Inbound message and OTP.ID matches the token automatically.
Delivery behaviour
OTP.ID delivers webhooks asynchronously — the HTTP POST to your endpoint happens in the background and does not block the API response your server receives. If delivery fails, OTP.ID retries according to the following policy:
Each attempt has a 10-second timeout. Any non-2xx HTTP response or network-level failure (connection refused, DNS error, timeout) counts as a failure and triggers the next retry. After the 3rd failed attempt, the event is abandoned.
Webhook payload
OTP.ID sends a JSON body with the following structure. The payload intentionally excludes the OTP code, any hash of it, or the raw verification token — only the metadata needed to identify and act on the event is included.The webhook payload never contains the OTP code itself, any hash of it, or the WhatsApp Inbound token. Store only what you need from the payload; do not log the full request body if your logs are broadly accessible.
Request headers
Every webhook request includes two security headers you must validate:Signature verification
OTP.ID signs each request so you can confirm it originated from OTP.ID and has not been tampered with. The signed message is constructed by concatenating the timestamp, a period, and the raw JSON body:webhook_secret (provided when your webhook URL is registered) and compare it to X-OTPID-Signature using a constant-time comparison to prevent timing attacks.
Make sure you compute the HMAC over the raw request body string, not a re-serialised version of the parsed JSON. JSON serialisers may reorder keys or change whitespace, which would produce a different digest and cause every signature check to fail.
Complete webhook handler example
The following examples show a minimal but secure webhook handler that verifies the signature before doing anything else.Best practices
Acknowledge quickly, process asynchronously. Your endpoint has 10 seconds to respond before OTP.ID considers the attempt failed and schedules a retry. Return200 OK as soon as the signature is validated, then process the event in the background. This keeps your handler fast and prevents spurious retries caused by slow database writes or downstream API calls.
Make your handler idempotent. OTP.ID may deliver the same event more than once if a retry fires after your first 200 was sent but not received. Use otp_id as a deduplication key — check whether you’ve already processed that transaction before taking action.
Correlate with your own IDs. Send an external_id on every request and read it back from the event to find the record this verification belongs to. Keep otp_id for deduplication: it is always present and always unique, while external_id is whatever you chose to send.
Reject invalid signatures with HTTP 401. Do not return 200 for requests that fail signature validation. A 200 response tells OTP.ID the event was received successfully and no retry is needed, which would suppress the legitimate delivery.
Keep your webhook_secret out of source code. Load it from an environment variable or a secrets manager. Contact the OTP.ID team to rotate it if you suspect it has been compromised.
