Skip to main content

Idempotency

Send an Idempotency-Key header on start (and on WhatsApp sends) to make retries safe. The key is 1 to 128 printable ASCII characters that you generate, scoped to your API key. Replaying it returns the original 201 unchanged, with the response header Idempotent-Replayed: true; nothing is sent or charged again. A send that fails releases the key so a retry can go through.
Use one key per business event, and retry a 502 or a network error with the same key.

Your own reference

reference (up to 64 printable characters) is echoed on start and on GET /v1/verify/{id}, and is searchable in the dashboard’s verifications list. Put your order id or session id there.

Rate limits

The two 429 codes share a status and differ in the error string. The per-key limit fails open if the limiter itself is unavailable.

The protection guard

Three per-application settings stop a runaway integration or an attacker from spending your balance. All are evaluated on start and resend, and skipped in the sandbox.
  • Daily spend cap (daily_spend_cap_halalas, off by default): today’s charges plus this send would pass the cap, so the send answers 429 spend_cap_reached. Days are Riyadh calendar days.
  • Per-IP cap (max_per_ip_per_hour, default 20): only checked when you pass client_ip on start. The address is never inferred from the request. Over the cap answers 429 ip_rate_limited.
  • New-destination pumping (max_new_destinations_per_hour, default 300): crossing it auto-pauses the application. Every call then answers 403 application_paused until the owner resumes it from the dashboard. The pause is audit-logged and the owner is notified.
Pass client_ip whenever the verification is started on behalf of an end user’s request. It is the one signal that lets Tawked tell many users apart from one abuser.

Verification lifecycle

resend keeps the id and issues a new code: the old code stops working, attempts and expiry reset, and the send is charged and rate-limited like any other. A new start never invalidates an earlier code. Read the current state at any time with GET /v1/verify/{id} without consuming an attempt.