otp.verified webhook is an outbound HTTP request that OTP.ID sends to your server when an OTP transaction reaches the verified state. Unlike the API endpoints documented elsewhere, you do not call this — you receive it. Configure your webhook URL in the OTP.ID dashboard. Every incoming request carries a cryptographic signature you must validate before trusting the payload.
Direction and Trigger
OTP.ID sends aPOST request to your configured webhook URL whenever a transaction is verified — either by a successful call to POST /v3/verify or by an automatic token match in a whatsapp_inbound flow. The delivery is asynchronous: the webhook fires after OTP.ID records the verification and may arrive a few seconds after the verified status is set.
If no webhook URL is configured in your OTP.ID dashboard, this event is silently dropped. The webhook is a no-op until you register a URL.
Request from OTP.ID
OTP.ID sends the following request to your endpoint:- Method:
POST - Content-Type:
application/json
Headers
Payload Fields
string
Always
"otp.verified". Use this field if your endpoint handles multiple event types.string
The verified transaction ID (e.g.
"OTP20260807ABCD000001").string
The delivery channel used for the transaction (e.g.
"whatsapp", "sms", "email").string
The destination phone number or email address associated with the transaction.
string
The datetime at which verification occurred, in
YYYY-MM-DD HH:MM:SS format (WIB, UTC+7).Payload Example
The payload never contains the OTP code, any hash or salt of the code, or inbound tokens. It carries only the identity and outcome of the transaction.
Signature Verification
Every request from OTP.ID includes anX-OTPID-Signature header. Always verify this signature before acting on the payload — it protects you against spoofed requests.
Algorithm (HMAC-SHA256):
- Read the
X-OTPID-Timestampheader value. - Read the raw, unparsed request body as a string (before JSON parsing).
- Concatenate:
"{timestamp}." + raw_body - Compute
HMAC-SHA256of that string using yourwebhook_secret(from the OTP.ID dashboard). - Compare the result (hex-encoded) with
X-OTPID-Signatureusing a constant-time comparison to prevent timing attacks.
Retry Policy
If your endpoint returns a non-2xx response or OTP.ID cannot reach it within the timeout, OTP.ID retries the delivery automatically:
Each attempt has a 10-second timeout. After 3 failed retries the event is dropped and not retried further.
Checklist for Your Endpoint
- ✅ Parse the raw body before any middleware that mutates it
- ✅ Validate
X-OTPID-Signatureusing constant-time comparison - ✅ Optionally reject requests where
X-OTPID-Timestampis too far in the past (e.g. > 5 minutes) to guard against replay attacks - ✅ Return HTTP 200 immediately; process async if needed
- ✅ Treat duplicate deliveries of the same
otp_ididempotently — retries may cause the same event to arrive more than once

