> ## 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.

# Idempotent OTP Requests Using the external_id Field

> Use external_id to make OTP creation requests safe to retry. Duplicate requests with the same external_id return the original transaction.

Network conditions are unpredictable. A request to `POST /v3/request` or `POST /v3/send` might time out before your code receives the response — but the server may have already processed it and charged your balance. If you retry blindly, you risk sending your user two OTP messages and paying twice. The `external_id` field solves this: by attaching your own unique key to a request, you make it safe to retry any number of times.

## What `external_id` does

Pass a unique string in the `external_id` field of any `POST /v3/request` or `POST /v3/send` call. OTP.ID stores this key alongside the transaction for **24 hours**. If you submit the same `external_id` again within that window with **identical parameters** (same `channel`, `number`, and — for `/v3/send` — the same `otp`), OTP.ID returns the **original transaction record** instead of creating a new one. No new message is sent to the user and no additional balance is deducted. This is called a **replay**.

## Why it matters

Without `external_id`, a network timeout forces you to choose between two bad options: retry (risk a duplicate send) or give up (the user never gets verified). With `external_id`, retrying is always safe — you get back the original result.

## Step-by-step example

### 1. First request

Your server sends the initial OTP request with a unique `external_id` tied to your own transaction or session.

```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",
    "number": "6281234567890",
    "brand": "TokoKita",
    "external_id": "order-8821"
  }'
```

**Response:**

```json theme={null}
{
  "success": true,
  "data": {
    "otp_id": "OTP20260807ABCD000001",
    "status": "sent",
    "channel": "whatsapp",
    "number": "6281234567890",
    "price": 350,
    "last_balance": 99650,
    "expires_at": "2026-08-07 10:05:00"
  },
  "error": null
}
```

### 2. Retry with the same parameters — replay

Your request timed out before you received the response above. You retry with the exact same body.

```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",
    "number": "6281234567890",
    "brand": "TokoKita",
    "external_id": "order-8821"
  }'
```

**Replay response — same `otp_id`, balance unchanged:**

```json theme={null}
{
  "success": true,
  "data": {
    "otp_id": "OTP20260807ABCD000001",
    "status": "sent",
    "channel": "whatsapp",
    "number": "6281234567890",
    "price": 350,
    "last_balance": 99650,
    "expires_at": "2026-08-07 10:05:00"
  },
  "error": null
}
```

OTP.ID returned the original transaction. No second message was sent. Your balance was not charged again.

### 3. Same `external_id`, different parameters — 409 error

If you accidentally (or intentionally) use the same `external_id` with **different** parameters — a different channel, number, or `otp` value — OTP.ID rejects the request with HTTP `409 DUPLICATE_EXTERNAL_ID`.

```bash theme={null}
curl -X POST https://api.otp.id/v3/request \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "sms",
    "number": "6281234567890",
    "external_id": "order-8821"
  }'
```

**409 response:**

```json theme={null}
{
  "success": false,
  "data": null,
  "error": {
    "code": "DUPLICATE_EXTERNAL_ID",
    "message": "external_id sudah pernah dipakai dengan parameter berbeda",
    "details": {
      "existing_otp_id": "OTP20260807ABCD000001"
    }
  }
}
```

The `error.details.existing_otp_id` field tells you the ID of the original transaction. Use `GET /v3/otp/{otp_id}` to retrieve its full details and determine the correct next step.

<Note>
  A `409 DUPLICATE_EXTERNAL_ID` is not a send failure — it means an OTP
  **was already sent** under this key. Inspect the original transaction before
  deciding whether to issue a new one.
</Note>

## Best practices

* **Use a meaningful, unique key.** Your order ID, session ID, checkout attempt UUID, or similar identifier works well. The key must be unique per OTP attempt — don't reuse keys across different verification events.
* **Always include `external_id` in production.** Transient network failures are common. Without `external_id`, any automated retry logic can send duplicate OTPs and charge your balance twice.
* **Treat `409` as an inspection prompt, not an error.** Fetch the original transaction with `GET /v3/otp/{existing_otp_id}`. If it's still pending and not expired, you can direct the user to verify with that transaction. Only create a new OTP if the original has expired or already been used.
* **Keep keys unique per attempt.** If a transaction expires and the user requests a new OTP, generate a fresh `external_id`. Reusing the old one will cause a `409` (the 24-hour window has not yet elapsed).
