> ## Documentation Index
> Fetch the complete documentation index at: https://docs.otp.id/llms.txt
> Use this file to discover all available pages before exploring further.

# WhatsApp Inbound OTP: Tap-to-Verify for Mobile Apps

> Use the whatsapp_inbound channel so users tap a deep-link to send a pre-filled WhatsApp message. No manual code typing required on your UI.

The `whatsapp_inbound` channel flips the normal OTP flow: instead of OTP.ID sending a code to the user, **the user sends a pre-filled message to OTP.ID**. This approach is popular in mobile apps because a single tap on a deep-link opens WhatsApp with the verification message already composed — no typing required. OTP.ID matches the token in the incoming message, marks the transaction as verified, and fires the `otp.verified` webhook to your server.

## How it works

<Steps>
  <Step title="Request a WhatsApp Inbound OTP">
    Call `POST /v3/request` with `"channel": "whatsapp_inbound"`. You do not need to supply `number` (see Binding vs. Unbound below).
  </Step>

  <Step title="Receive the verification block">
    The API response includes a `verification` object containing OTP.ID's central WhatsApp number, the exact message text the user must send, and a deep-link that pre-fills the message in WhatsApp.
  </Step>

  <Step title="Display the deep-link or message to the user">
    Show a **"Verify via WhatsApp"** button powered by `wa_link`, or display the message text so the user can copy and send it manually.
  </Step>

  <Step title="User sends the message">
    The user taps your button (or manually sends the message) to OTP.ID's WhatsApp number. The message text must be sent **verbatim** — any modification breaks the token match.
  </Step>

  <Step title="OTP.ID matches the token and verifies">
    OTP.ID receives the message, matches the embedded token against the pending transaction, marks it as verified, and fires the `otp.verified` webhook to your server.
  </Step>
</Steps>

## Sending the request

```bash theme={null}
curl -X POST https://api.otp.id/v3/request \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp_inbound",
    "brand": "TokoKita",
    "ttl": 300
  }'
```

**Request body:**

```json theme={null}
{
  "channel": "whatsapp_inbound",
  "brand": "TokoKita",
  "ttl": 300
}
```

## Response with the `verification` block

A successful request returns a `verification` object in addition to the standard transaction fields. The `status` is `"pending"` — it transitions to `"verified"` when the user sends the correct message.

```json theme={null}
{
  "success": true,
  "data": {
    "otp_id": "OTP20260807ABCD000002",
    "status": "pending",
    "channel": "whatsapp_inbound",
    "number": "",
    "price": 350,
    "last_balance": 99300,
    "expires_at": "2026-08-07 10:05:00",
    "verification": {
      "wa_number": "6285212345678",
      "message": "OTPID V-8FK2QN9P — verifikasi TokoKita. Kirim pesan ini tanpa mengubah isinya.",
      "wa_link": "https://wa.me/6285212345678?text=OTPID%20V-8FK2QN9P%20%E2%80%94%20verifikasi%20TokoKita.%20Kirim%20pesan%20ini%20tanpa%20mengubah%20isinya.",
      "expires_at": "2026-08-07 10:05:00"
    }
  },
  "error": null
}
```

### `verification` field reference

| Field        | Description                                                         |
| ------------ | ------------------------------------------------------------------- |
| `wa_number`  | OTP.ID's central WhatsApp number the user must send to              |
| `message`    | The exact text the user must send, verbatim                         |
| `wa_link`    | A `wa.me` deep-link that opens WhatsApp with the message pre-filled |
| `expires_at` | When the token expires (matches the top-level `expires_at`)         |

<Tip>
  In mobile apps, render `wa_link` as a prominent button — for example, **"Verify via WhatsApp"**. On Android and iOS, tapping this link opens WhatsApp directly with the correct message pre-filled in the compose field. The user only needs to tap **Send**. This dramatically improves completion rates compared to asking the user to copy and paste a code.
</Tip>

## Binding vs. unbound

You can optionally include a `number` in your request to restrict verification to a specific phone number.

| Mode        | Request                      | Behaviour                                                                                                         |
| ----------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| **Unbound** | No `number` field            | Any WhatsApp user who sends the correct token is accepted. Useful for anonymous or pre-login flows.               |
| **Bound**   | Include `"number": "628..."` | OTP.ID only accepts the message if it comes from that specific WhatsApp number. Adds an extra layer of assurance. |

**Bound request example:**

```json theme={null}
{
  "channel": "whatsapp_inbound",
  "number": "6281234567890",
  "brand": "TokoKita",
  "ttl": 300
}
```

## Handling completion

Because the user acts in a separate app, your server learns about verification via the **`otp.verified` webhook** rather than a synchronous API response. In your UI, you can:

1. **Poll** `GET /v3/otp/{otp_id}` every few seconds and check `status === "verified"`.
2. **Listen for the webhook** (recommended) and push a notification to your frontend via WebSocket or SSE when the event arrives.

See the [Webhooks guide](/guides/webhooks) for payload details and how to verify the webhook signature.

## Limitations

<Note>
  `whatsapp_inbound` is **not** supported by `POST /v3/send`. You must use
  `POST /v3/request` for this channel. Additionally, `POST /v3/verify` is not
  called by your server for this flow — verification happens automatically when
  OTP.ID receives the user's WhatsApp message.
</Note>
