Verification & messages
Start a verification
Generates a code and sends it along the channel chain, with automatic fallback and refunds.
https://watsy.me/v1/verifyThe code is generated on our server, never appears in a response and is never stored: only an HMAC is kept. Channels are tried in order; if one rejects or does not answer within 6 seconds, it is refunded and the next one is tried.
curl -X POST https://watsy.me/v1/verify \
-H "Authorization: Bearer $WATSY_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: signup-8841" \
-d '{
"phone": "07701234567",
"locale": "ar",
"client_ip": "198.51.100.23",
"metadata": {
"user_id": 8841
}
}'{
"id": "5d3c1f0e-7a1b-4c55-9a7e-2f1d6b0c9e11",
"object": "verification",
"mode": "live",
"phone": "+9647701234567",
"status": "sent",
"channel": "telegram",
"channels": [
"whatsapp",
"telegram",
"sms"
],
"attempts": 0,
"max_attempts": 5,
"attempts_remaining": 5,
"locale": "ar",
"metadata": {
"user_id": 8841
},
"expires_at": "2026-10-28T10:05:00.000Z",
"verified_at": null,
"created_at": "2026-10-28T10:00:00.000Z",
"price_milli": 18000,
"messages": [
{
"id": "0b6f6a3e-1c2d-4e5f-8a9b-1c2d3e4f5a6b",
"object": "message",
"verification_id": "5d3c1f0e-7a1b-4c55-9a7e-2f1d6b0c9e11",
"channel": "whatsapp",
"provider": "meta_cloud",
"status": "failed",
"error_code": "whatsapp_131026",
"price_milli": 0,
"created_at": "2026-10-28T10:00:00.000Z",
"updated_at": "2026-10-28T10:00:01.000Z"
},
{
"id": "7e1d2c3b-4a59-4687-9a0b-c1d2e3f4a5b6",
"object": "message",
"verification_id": "5d3c1f0e-7a1b-4c55-9a7e-2f1d6b0c9e11",
"channel": "telegram",
"provider": "telegram_gateway",
"status": "sent",
"error_code": null,
"price_milli": 18000,
"created_at": "2026-10-28T10:00:01.000Z",
"updated_at": "2026-10-28T10:00:02.000Z"
}
]
}Authorization
Authorizationstring (header)requiredBearerfollowed by your project key:wsy_live_…orwsy_test_….
Headers
Idempotency-Keystring1 to 255 characters. Makes retries safe for 24 hours. See Idempotency.
X-Request-IdstringOptional, 8 to 64 characters. Echoed back; generated when missing.
Body
application/jsonphonestringrequiredRecipient number in any supported format.
channelsstring[]Channel order, 1 to 3 without duplicates. Defaults to the project setting (
whatsapp, thentelegram, thensms).Values:
whatsapptelegramsmscode_lengthintegerdefault: 6Code length, 4 to 8. Defaults to the project setting.
ttl_secondsintegerdefault: 300Code lifetime, 60 to 1800 seconds.
localestringdefault: arMessage language.
Values:
arenmetadataobjectYour own data (up to 2KB, keys up to 64 characters). Echoed in responses and webhooks.
client_ipstringThe end user's IP (from your app's request, IPv4 or IPv6). The per-IP rate limit counts only this; without it, no per-IP limit applies. Always send it so one person can't request codes for many numbers.
Response (201)
application/jsonidstringrequiredVerification id (UUID).
objectstringrequiredAlways
verification.modestringrequiredMode of the key that created it.
Values:
livetestphonestringrequiredThe normalized E.164 number.
statusstringrequiredLifecycle state. See the status table.
Values:
pendingsentverifiedfailedexpiredcanceledchannelstring | nullrequiredThe channel last tried, or the one that delivered.
channelsstring[]requiredThe requested channel chain, in order.
attemptsintegerrequiredChecks made so far.
max_attemptsintegerrequiredCheck limit from the project settings (default 5).
attempts_remainingintegerrequiredChecks left.
localestringrequiredMessage language.
Values:
arenmetadataobjectrequiredYour metadata as sent, or
{}.expires_atstringrequiredWhen the code expires.
verified_atstring | nullrequiredWhen it was verified.
created_atstringrequiredCreation time.
price_milliintegerrequiredNet charge across all attempts, in milli-IQD (1 IQD = 1000).
messagesmessage[]requiredOne entry per channel tried, in order.
Show child fieldsHide child fields
idstringId of this channel attempt.
objectstringAlways
message.verification_idstringThe verification this attempt belongs to.
channelstringChannel.
Values:
whatsapptelegramsmsproviderstringWho carried the attempt:
meta_cloud(Watsy's WhatsApp number),whatsapp_own(your own number),telegram_gateway, ortestin test mode.statusstringdeliveredappears once the provider reports delivery (WhatsApp today).Values:
pendingsentdeliveredfailederror_codestring | nullProvider failure reason, e.g.
whatsapp_131026,telegram_PHONE_NUMBER_INVALIDortimeout.price_milliintegerNet charge of this attempt in milli-IQD. A refunded attempt shows 0.
created_atstringCreation time (ISO 8601, UTC).
updated_atstringLast update.
How charging works
- Each channel is charged before we call the provider and refunded at once if it fails. A failed channel therefore shows a price of 0, and
price_milliis the net amount. - If the balance cannot cover the first channel you get
402 insufficient_balanceand nothing is created or sent. - When every channel fails you get
502 delivery_failedwith a full refund, and theverification.failedevent fires.
Other errors
| HTTP | code | When |
|---|---|---|
402 | spend_cap_reached | The monthly spend cap would be exceeded |
403 | forbidden | Your server's IP is outside the allowlist |
403 | test_number_required | Test key with a number that is not a registered test number |
409 | idempotency_conflict | Idempotency-Key conflict |
503 | no_channel_available | No configured and priced channel for this destination |