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.
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.
/services?country=US&q=whatsapp
/balance
/rent
/status?id=123
/active
/cancel
/finish
/history?page=1&status=received
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"
}
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"
}
}
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);
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.