# Verify (/verify)



Verify sends a one-time code and checks it for you. You never generate, store or
compare codes yourself.

## Two calls [#two-calls]

Start a verification for the person who is signing in:

```bash
curl https://app.sendkuy.com/api/v1/verifications \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to": "+6281234567890", "brand": "Toko Andi"}'
```

Then check the code they type:

```bash
curl https://app.sendkuy.com/api/v1/verifications/vrf_7hTq.../check \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"code": "123456"}'
```

A match answers `200` with `status: approved`, and the code is used up. A wrong
code answers `verification_code_invalid`; five wrong codes fail the
verification. A code past its `ttl` (600 seconds by default) answers
`verification_expired`.

Starting a new verification for the same `to` or `email` cancels the one still
pending, so only the newest code works. A channel your plan does not include is
refused up front with `channel_not_in_plan`.

Once a verification is approved, failed, cancelled or expired, any of its
messages that have not gone out yet are cancelled, so nobody receives a code
that no longer works.

## Channels and fallback [#channels-and-fallback]

`channels` is the order we try, `["whatsapp", "sms"]` by default. We move to the
next channel when:

* nothing on the channel can send for you — no sender, or no credit left today
  (the attempt says `skipped`);
* the send fails on every sender you have on that channel (`failed`) — a sender
  that cannot send, such as a disconnected device or a phone without signal, is
  first swapped for your next live one, carrying the same code;
* a WhatsApp device sent it but no delivery receipt arrived within 30 seconds
  (`undelivered`).

SMS and email count as done once they are handed over, because neither reports
delivery. The same code goes out on every channel. The first send happens inside
your request, so the response already says how it went. When that sender failed
and the code moved to another of yours, the attempt already reads `sent`; if the
next sender fails too, it turns `failed` and the next channel follows. That step
and the 30-second fallback run in the background and are on time only when your
SendKuy server runs its workers under supervisor.

## WhatsApp Cloud API [#whatsapp-cloud-api]

When you own a Cloud API number, Verify sends through Meta's **Authentication**
template instead of a plain message:

1. Create a template with the Authentication category in Meta Business Manager,
   with a copy-code button.
2. Sync templates in SendKuy once Meta approves it.

We pick the one whose language matches `locale`, or any Authentication template
when none does. Templates that share a name keep only one language after a
sync, so give each language its own name.

## What the person receives [#what-the-person-receives]

The text is fixed so Verify cannot be used to send anything else. Only `brand`
changes:

* `id`: "123456 adalah kode verifikasi Toko Andi. Berlaku 10 menit. Jangan
  berikan kode ini kepada siapa pun."
* `en`: "123456 is your Toko Andi verification code. It expires in 10 minutes.
  Do not share it with anyone."

The code never appears in `GET /messages`, in webhook payloads or in your
dashboard: the stored message holds `{{verification_code}}` instead.

## Credits, contacts and opt-outs [#credits-contacts-and-opt-outs]

* Each message spends one credit of its own channel. A fallback from WhatsApp to
  SMS spends both when both were sent. A skipped channel spends nothing.
* The number or email becomes a contact on your account, as with
  `POST /messages`, and fires `contact.created`.
* Someone who opted out of your promotions still gets their code. Addresses that
  bounced, complained or were blocked by SendKuy do not, and Verify moves to the
  next channel.

## Limits [#limits]

Five verifications per destination every ten minutes, and five code checks per
verification. See [Rate limits](/rate-limits).
