textsms.io
Log in Sign up

API & webhooks

Rent numbers and collect codes from your own code. Send us a key, and we'll push each code to your server the moment it lands.

Your key lives in your account

Create an account to get an API key, point a webhook at your server and see every delivery we attempt. The reference below is the whole contract, so you can read it first and decide.

Reference
Base URL https://textsms.io/api/v1
Endpoints
GET /services?country=US&q=whatsapp
The catalogue. Use each row's id to rent.
GET /balance
Wallet balance, in NGN and USD.
POST /rent
{"service_id": 42}. Charges your wallet and returns a number. Send an Idempotency-Key header so a retry can't buy twice.
GET /status?id=123
One rental, checking for a code as it answers. Use webhooks instead where you can.
GET /active
Everything still outstanding. Does not poll for codes.
POST /cancel
{"id": 123}. Refunds a number that never got a code.
POST /finish
{"id": 123}. Closes one you're done with. No refund.
GET /history?page=1&status=received
Closed rentals, 25 per page.
Renting a number
curl -X POST https://textsms.io/api/v1/rent \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-4471" \
  -d '{"service_id": 42}'

{
  "ok": true,
  "rental": {
    "id": 8891,
    "order_id": "9f2c41a0be77d1c4e5aa",
    "service": "Whatsapp",
    "number": "+13024015512",
    "code": "",
    "status": "waiting",
    "price": 910,
    "currency": "NGN",
    "expires_in": 1200
  },
  "balance": 49090,
  "currency": "NGN"
}
What we send you

Events: number.code_received, number.expired, number.cancelled, webhook.test.

POST /your/endpoint
X-Textsms-Event:     number.code_received
X-Textsms-Delivery:  4471
X-Textsms-Timestamp: 1787772256
X-Textsms-Signature: sha256=088c1cdf29c3c511d7378d9ef5...

{
  "event": "number.code_received",
  "created_at": "2026-08-26T19:23:15+00:00",
  "data": {
    "id": 8891,
    "order_id": "9f2c41a0be77d1c4e5aa",
    "service": "Whatsapp",
    "number": "+13024015512",
    "code": "994213",
    "status": "received",
    "price": 910,
    "currency": "NGN"
  }
}
Verifying the signature

The signature covers the timestamp and the raw body together, so a captured request cannot be replayed later, so reject anything older than five minutes. Compare with a constant-time function, never ==.

<?php
$secret = 'YOUR_SIGNING_SECRET';
$body   = file_get_contents('php://input');
$ts     = $_SERVER['HTTP_X_TEXTSMS_TIMESTAMP'] ?? '';
$sig    = $_SERVER['HTTP_X_TEXTSMS_SIGNATURE'] ?? '';

$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, $secret);

if (!hash_equals($expected, $sig) || abs(time() - (int) $ts) > 300) {
    http_response_code(400);
    exit;
}

$event = json_decode($body, true);
// $event['data']['code'] is your OTP.

http_response_code(200);
Limits and errors

60 requests per minute, and 20 rentals per minute, per key. Every response carries X-RateLimit-Remaining; going over returns 429.
Failures always look like {"ok":false,"error":{"code":"…","message":"…"}}. Branch on code, not on the message text.
Numbers expire 20 minutes after renting and are refunded automatically.