> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.voicebip.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.voicebip.com/_mcp/server.

# SMS

> Send and receive SMS messages through direct SMPP connections to Nigerian MNOs.

Voicebip sends and receives SMS through persistent SMPP v3.4 sessions to all four Nigerian MNO SMSCs — MTN, Glo, Airtel, and 9mobile. Messages are routed by number prefix with automatic failover.

## Sending an SMS

Send an outbound SMS via the unified messages endpoint:

```bash
curl -X POST "https://api.voicebip.com/v1/messages" \
  -H "Authorization: Bearer pk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agt_PAEZ_njcfm2kycpjs",
    "channel": "sms",
    "from_number": "+2348031000001",
    "to_number": "+2348061234567",
    "body": "Your verification code is 482910. It expires in 10 minutes."
  }'
```

Response:

```json
{
  "message_id": "msg_k7Tz9mWvXp3nR",
  "channel": "sms",
  "status": "queued",
  "from_number": "+2348031000001",
  "to_number": "+2348061234567",
  "segments": 1,
  "created_at": "2026-04-09T12:00:00Z"
}
```

The message is queued for delivery via the appropriate MNO SMPP session. You receive delivery status updates through webhooks.

## Receiving Inbound SMS

When a user sends an SMS to one of your provisioned +234 numbers, Voicebip delivers a `message.received` webhook to your endpoint:

```json
{
  "event_id": "evt_Rj4kLm8nQ",
  "event_type": "message.received",
  "channel": "sms",
  "agent_id": "agt_PAEZ_njcfm2kycpjs",
  "number": "+2348031000001",
  "from": "+2348061234567",
  "timestamp": "2026-04-09T12:05:00Z",
  "payload": {
    "message_id": "msg_Xp7nR3kTz9mW",
    "body": "What is my account balance?",
    "encoding": "GSM-7"
  }
}
```

Your agent can process the inbound message and reply using the same `POST /v1/messages` endpoint.

## Auto-Segmentation

SMS messages are automatically split into segments when they exceed character limits:

| Encoding                          | Single SMS limit | Per-segment limit (multipart) |
| --------------------------------- | ---------------- | ----------------------------- |
| GSM-7 (Latin characters, digits)  | 160 characters   | 153 characters                |
| UCS-2 (Unicode, emoji, non-Latin) | 70 characters    | 67 characters                 |

The `segments` field in the response tells you how many segments the message was split into. Each segment is billed independently.

> **Note**
>
> Voicebip detects the required encoding automatically. If your message contains any non-GSM-7 character (emoji, accented characters, CJK), the entire message is encoded as UCS-2.

## Delivery Receipts

Voicebip requests delivery receipts (DLRs) from the MNO for every outbound message. When the MNO confirms delivery, a `message.dlr` webhook is sent:

```json
{
  "event_id": "evt_Np2xVb5kQ",
  "event_type": "message.dlr",
  "channel": "sms",
  "agent_id": "agt_PAEZ_njcfm2kycpjs",
  "timestamp": "2026-04-09T12:00:03Z",
  "payload": {
    "message_id": "msg_k7Tz9mWvXp3nR",
    "status": "DELIVRD",
    "error_code": "000",
    "submitted": 1,
    "delivered": 1
  }
}
```

The `status` field carries the raw SMPP delivery-state code from the SMSC. Possible values: `DELIVRD` (delivered), `UNDELIV` (undeliverable/failed), `REJECTD` (rejected), `EXPIRED` (validity period elapsed), or `UNKNOWN`. When the platform can map the SMSC message id back to the original `msg_*` id it also includes `smsc_message_id`.

## STOP Opt-Out Handling

Voicebip includes built-in opt-out keyword handling to comply with messaging regulations. When a recipient replies with any of these keywords, they are automatically opted out:

* **STOP**
* **UNSUBSCRIBE**
* **CANCEL**
* **END**
* **QUIT**

Keyword matching is case-insensitive. Once a number is opted out, any attempt to send an SMS to that number returns a `422` error:

```json
{
  "error_code": "OPT_OUT_ENFORCED",
  "message": "Recipient +2348061234567 has opted out of SMS from this agent.",
  "request_id": "req_abc123",
  "documentation_url": "https://docs.voicebip.com/errors/OPT_OUT_ENFORCED"
}
```

Opt-out state is tracked per agent and is durable: there is **no self-service keyword** (such as START or SUBSCRIBE) to opt back in. Once a number sends a stop keyword it stays opted out, so treat opt-outs as permanent.

## MNO Routing

Voicebip routes each SMS to the correct MNO SMSC based on the recipient's phone number prefix:

| MNO     | Prefixes                                       |
| ------- | ---------------------------------------------- |
| MTN     | 0803, 0806, 0703, 0706, 0813, 0816, 0810, 0814 |
| Glo     | 0805, 0807, 0705, 0815, 0811                   |
| Airtel  | 0802, 0808, 0708, 0812                         |
| 9mobile | 0809, 0817, 0818, 0908, 0909                   |

If the prefix is unrecognized, the message is routed to MTN as the default carrier.

### Failover

If the primary MNO SMSC is unreachable, Voicebip fails over through the chain: **MTN -> Glo -> Airtel -> 9mobile**. Failover is automatic and transparent — the API response is the same regardless of which SMSC delivers the message.

## SMPP Infrastructure

Voicebip maintains one persistent SMPP v3.4 session per MNO SMSC. Key details:

* **Keep-alive**: `ENQUIRE_LINK` every 30 seconds
* **Throughput**: bounded by MNO-allocated TPS per session
* **Connection recovery**: automatic reconnect with exponential backoff on session loss

## Sandbox Testing

Use a `pk_test_` API key to send SMS in sandbox mode. Sandbox messages are not delivered to real MNO networks — synthetic NATS events simulate the full lifecycle (queued, delivered). Billing is NGN 0.

Sandbox numbers: `+234800000xxxx` (test mobile virtual).

```bash
curl -X POST "https://api.voicebip.com/v1/messages" \
  -H "Authorization: Bearer pk_test_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agt_PAEZ_njcfm2kycpjs",
    "channel": "sms",
    "from_number": "+2348000001000",
    "to_number": "+2348000002000",
    "body": "Hello from sandbox"
  }'
```