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. To activate it, contact the OTP.ID team to register your webhook URL and receive your webhook_secret (self-service configuration from the dashboard is coming soon). 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 registered for your account, 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
external_id you sent when creating the transaction, echoed back so you can match this event to your own record without an extra lookup. Always present — an empty string ("") when you did not send one. See Idempotency.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(provided when your webhook URL is registered). - 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. Delivery is attempted up to 3 times in total:
Each attempt has a 10-second timeout, and
X-OTPID-Timestamp (and therefore the signature) is regenerated per attempt. After the 3rd failed attempt 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

