جائزہ
ڈویلپر API ایپ والے نمبر اسی بیلنس سے، REST API اور ایک موافق اینڈ پوائنٹ کے ذریعے فروخت کرتی ہے۔
- Base URL
https://smsgrab.com/api/dev/v1- sms-activate compatible
https://smsgrab.com/api/stubs/handler_api.php- Format
- JSON over HTTPS,
snake_casefields, UTF-8. Money is an integer in minor units ofcurrency(USD cents). Timestamps are ISO 8601 in UTC with milliseconds. - Authentication
- An API key (
sgk_…) inAuthorization: BearerorX-Api-Key. - Money
- Every charge goes through the same double-entry ledger as the app and the website. Numbers without a code are refunded automatically.
- Request ids
- Every answer carries
X-Request-Id. Include it when you contact support.
کوئیک اسٹارٹ
تین کالز آپ کو نئی کی سے موصول شدہ کوڈ تک لے جاتی ہیں۔
- تصدیق شدہ ای میل کے ساتھ اکاؤنٹ بنائیں اور ڈویلپر سیکشن کھولیں۔
- "پڑھنا اور خریدنا" رسائی والی کی بنائیں اور اس کا سیکرٹ کاپی کریں۔
- یقینی بنائیں کہ بیلنس مطلوبہ نمبر کے لیے کافی ہے، پھر نیچے دی گئی کالز چلائیں۔
export SMSGRAB_API_KEY="sgk_your_key"
curl -s https://smsgrab.com/api/dev/v1/balance \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
curl -s -X POST https://smsgrab.com/api/dev/v1/activations \
-H "Authorization: Bearer $SMSGRAB_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"service":"telegram","country":"indonesia","max_price_minor":30}'
curl -s "https://smsgrab.com/api/dev/v1/activations/100000123?wait=25" \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
curl -s -X POST https://smsgrab.com/api/dev/v1/activations/100000123/finish \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
The number is yours for 20 minutes. Poll GET /activations/{id}?wait=25 (the request is held until something changes) or subscribe to webhooks. When a code has arrived, finish the activation; if none arrives, it expires and the charge is refunded automatically. Complete programs in five languages are in مکمل مثالیں.
تصدیق
ہر درخواست میں آپ کی کوئی ایک API کی ہوتی ہے۔
GET /api/dev/v1/balance HTTP/1.1
Host: smsgrab.com
Authorization: Bearer sgk_your_key
X-Api-Key: sgk_… works as well. A key in a query string is accepted only by the compatible endpoint. There is no CORS: keys belong on servers, never in browsers or apps you ship to users.
| Situation | Answer |
|---|---|
| No key, malformed or unknown key | 401 UNAUTHORIZED · API_KEY_INVALID |
| Revoked key | 401 UNAUTHORIZED · API_KEY_REVOKED |
| Expired key, or an old secret after its rotation grace | 401 UNAUTHORIZED · API_KEY_EXPIRED |
| Address outside the key's allowlist | 403 FORBIDDEN · API_KEY_IP_NOT_ALLOWED |
| The key lacks the needed scope | 403 FORBIDDEN · API_KEY_SCOPE |
| E-mail no longer verified, account suspended, or developer access blocked | 403 FORBIDDEN · EMAIL_NOT_VERIFIED, ACCOUNT_BLOCKED, DEVELOPER_ACCESS_BLOCKED |
| Too many rejected keys from one network | 429 RATE_LIMITED |
API کیز
کیز آپ کے اکاؤنٹ میں بنائی، محدود اور تبدیل کی جاتی ہیں۔
| Format | sgk_ followed by 40 letters and digits. Shown once, when it is created or rotated. We store only a keyed hash. |
|---|---|
| Scopes | read (balance, catalogue, prices, activations, usage) or read + purchase (buy, cancel, finish). |
| IP allowlist | Up to 50 IPv4/IPv6 addresses or CIDR ranges. Empty means any address. |
| Daily spend cap | Optional, per UTC day, counted over the key's net charges. Purchases above it answer 403 DAILY_SPEND_CAP_REACHED and nothing is charged. |
| Expiry | Optional, at most two years ahead. |
| Rotation | A new secret for the same key, settings, usage and webhooks. The old secret keeps working for the grace period you choose (up to 72 hours). |
| Revocation | Immediate and final. The key's webhooks are switched off. |
| Per account | Up to 10 active keys. |
Creating or rotating a key, adding a webhook, rolling its secret and widening a key's access (more scopes, a wider allowlist, a higher cap or a later expiry) ask for your password again, and your two-step code when it is on. Revoking and tightening never do.
قیمتیں
آپ کی قیمت ہر آفر کی ہول سیل لاگت اور والیوم کے ساتھ کم ہونے والے مارک اپ پر مبنی ہے۔
developer_price = max(ceil(wholesale × (100 + markup) / 100), minimum)
developer_price = min(developer_price, app_price)
| 30 دن کا ڈویلپر خرچ | ہول سیل پر مارک اپ | مثالی قیمت |
|---|---|---|
| $100.00 تک | +80% | $0.18 ایپ سے 28% کم |
| $100.00 سے | +70% | $0.17 ایپ سے 32% کم |
| $500.00 سے | +60% | $0.16 ایپ سے 36% کم |
| $2,000.00 سے | +50% | $0.15 ایپ سے 40% کم |
The tier follows your trailing 30-day developer spend (charges minus refunds of numbers bought through the API or the compatible endpoint) and applies within five minutes of crossing a threshold. The minimum price is $0.10. A developer price is never above the price of the same number in the app. The price is fixed at purchase time; send max_price_minor to protect yourself from a change between reading prices and buying. Your current markup and tier are in GET /me.
شرح کی حدود
حدود پلیٹ فارم کو سب کے لیے تیز رکھتی ہیں۔ زیادہ تر انٹیگریشنز کبھی ان تک نہیں پہنچتے۔
| Limit | Default | When exceeded |
|---|---|---|
| Requests per key | 600 / minute | 429 RATE_LIMITED with Retry-After |
| Purchases per key | 60 / minute | 429 RATE_LIMITED |
| Requests per account (all keys) | 3,000 / minute | 429 RATE_LIMITED |
| Numbers waiting at once | 100 | 422 VALIDATION · ACTIVATION_LIMIT_REACHED |
| Held long-poll requests per key | 20 | 429 RATE_LIMITED |
| Webhooks per key | 3 | 422 VALIDATION · WEBHOOK_LIMIT_REACHED |
Every answer carries RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and RateLimit-Policy for the tightest limit that applies.
غلطیاں
ناکام کالز ایک مستحکم کوڈ اور جہاں مفید ہو، زیادہ درست وجہ دیتی ہیں۔
{
"code": "NO_NUMBERS_AVAILABLE",
"message": "No numbers available for this service and country",
"reason": "OUT_OF_STOCK"
}
| HTTP | code | reason | Meaning |
|---|---|---|---|
400 / 422 | VALIDATION | ACTIVATION_LIMIT_REACHED, ACTIVATION_CODE_RECEIVED, ACTIVATION_FINISHED, ACTIVATION_NO_CODE_YET, ACTIVATION_CANCEL_NOT_YET | A field is malformed or the activation is in the wrong state. The message names the field. |
401 | UNAUTHORIZED | API_KEY_INVALID, API_KEY_REVOKED, API_KEY_EXPIRED | No key, an unknown key, a revoked key, or an old secret after its rotation grace. |
402 | INSUFFICIENT_BALANCE | – | The balance does not cover the price. Nothing is charged. |
403 | FORBIDDEN | API_KEY_IP_NOT_ALLOWED, API_KEY_SCOPE, EMAIL_NOT_VERIFIED, ACCOUNT_BLOCKED, DEVELOPER_ACCESS_BLOCKED, DAILY_SPEND_CAP_REACHED | The key or the account may not do this right now. |
404 | NOT_FOUND, SERVICE_NOT_FOUND, COUNTRY_NOT_FOUND | ACTIVATION_NOT_FOUND | Unknown id, service or country. |
409 | NO_NUMBERS_AVAILABLE | OUT_OF_STOCK, PROVIDER_OUT_OF_NUMBERS | No number could be delivered. Any charge was refunded in the same request. |
409 | PRICE_CHANGED | – | The price is above max_price_minor. Nothing is charged. |
429 | RATE_LIMITED | – | Slow down and retry after the Retry-After header. |
503 | PROVIDER_UNAVAILABLE | PURCHASES_PAUSED, PROVIDER_PAUSED, PROVIDER_CIRCUIT_OPEN, SERVER_DRAINING | Purchases are paused for a moment. Nothing is charged. |
503 | FEATURE_UNAVAILABLE | – | The developer API is switched off for maintenance. |
5xx | INTERNAL | – | Retry idempotent requests with exponential backoff. |
New reason values may be added at any time; treat unknown ones like their code.
Idempotency
خریداری کی محفوظ دوبارہ کوشش کبھی دو بار نہیں خریدتی۔
Send an Idempotency-Key header (any unique string up to 64 characters, a UUID is ideal) with POST /activations. If the network drops and you retry with the same key, you get the first answer again with Idempotent-Replayed: true instead of a second number. Cancel and finish are idempotent by nature: repeating them when the activation already has that status succeeds.
اینڈ پوائنٹس
تمام اینڈ پوائنٹس بیس URL کے تحت ہیں اور JSON دیتے ہیں۔
- GET
/meکال کرنے والی کی، آپ کی قیمت اور حدود۔ - GET
/balanceآپ کے اکاؤنٹ کا بیلنس۔ - GET
/countriesجن ممالک میں نمبر دستیاب ہیں۔ - GET
/servicesسروسز، اختیاری طور پر صرف ایک ملک میں دستیاب۔ - GET
/pricesہر آفر کی ڈویلپر قیمتیں اور دستیابی۔ - POST
/activationsنمبر خریدیں۔ - GET
/activationsآپ کی ایکٹیویشنز کی فہرست۔ - GET
/activations/{id}ایک ایکٹیویشن، اختیاری long polling کے ساتھ۔ - POST
/activations/{id}/cancelکوڈ آنے سے پہلے منسوخ کریں اور ریفنڈ لیں۔ - POST
/activations/{id}/finishکوڈ کے بعد ایکٹیویشن مکمل کریں۔ - GET
/usageوقت کے ساتھ درخواستیں، خریداریاں اور خرچ۔
GET
/me
کال کرنے والی کی، آپ کی قیمت اور حدود۔ اجازت: read
curl -s https://smsgrab.com/api/dev/v1/me \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{
"key": { "id": "0b8e2a41-7c1d-4e5f-9a3b-6c7d8e9f0a1b", "name": "production bot", "display_prefix": "sgk_4hT9wQ", "last4": "Xa7Q",
"scopes": ["read", "purchase"], "ip_allowlist": ["203.0.113.0/24"], "daily_spend_cap_minor": 5000,
"rate_limit_per_minute": 600, "purchase_limit_per_minute": 60, "expires_at": null },
"account": { "user_id": "2b4b9c1d-3e5f-4a6b-8c7d-9e0f1a2b3c4d", "email": "dev@example.org" },
"currency": "USD",
"pricing": { "markup_percent": 70, "source": "tier", "min_price_minor": 10, "cap_at_retail": true,
"trailing_30d_spend_minor": 18250,
"tier": { "min_spend_minor": 10000, "markup_percent": 70 },
"next_tier": { "min_spend_minor": 50000, "markup_percent": 60, "remaining_minor": 31750 } },
"limits": { "max_active_activations": 100, "active_activations": 3, "spend_today_minor": 420 }
}
GET
/balance
آپ کے اکاؤنٹ کا بیلنس۔ اجازت: read
curl -s https://smsgrab.com/api/dev/v1/balance \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "balance_minor": 12345, "currency": "USD", "updated_at": "2026-09-27T10:02:11.482Z" }
GET
/countries
جن ممالک میں نمبر دستیاب ہیں۔ اجازت: read
curl -s https://smsgrab.com/api/dev/v1/countries \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "items": [ { "id": "indonesia", "name": "Indonesia", "iso_code": "ID", "dial_code": "62", "flag_emoji": "🇮🇩",
"available_numbers": 812334, "service_count": 402, "compat_id": 6 } ] }
GET
/services
سروسز، اختیاری طور پر صرف ایک ملک میں دستیاب۔ اجازت: read
| Parameter | In | Type | Description |
|---|---|---|---|
country | query | string | Only services in stock in this country. With it, min_price_minor is your price there and country_count is omitted. |
curl -s "https://smsgrab.com/api/dev/v1/services?country=indonesia" \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "items": [ { "id": "whatsapp", "name": "WhatsApp", "category": "MESSAGING",
"icon_url": "https://smsgrab.com/api/v1/icons/whatsapp?v=1fceb4feb3", "compat_code": "wa",
"min_price_minor": 17 } ] }
GET
/prices
ہر آفر کی ڈویلپر قیمتیں اور دستیابی۔ اجازت: read
| Parameter | In | Type | Description |
|---|---|---|---|
service | query | string | A service id. Leave it out for every service. |
country | query | string | A country id. Leave it out for every country. |
curl -s --compressed "https://smsgrab.com/api/dev/v1/prices?service=whatsapp&country=indonesia" \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "currency": "USD", "markup_percent": 70,
"items": [ { "service_id": "whatsapp", "country_id": "indonesia", "price_minor": 17, "retail_price_minor": 25,
"available_numbers": 30412 } ] }
POST
/activations
نمبر خریدیں۔ اجازت: purchase
| Parameter | In | Type | Description |
|---|---|---|---|
service required | body | string | A service id (whatsapp) or its compatible code (wa). |
country required | body | string | A country id (indonesia), an ISO code (ID) or a compatible id (6). |
max_price_minor | body | integer | The most you want to pay, in cents. A higher price answers 409 PRICE_CHANGED and nothing is charged. |
Idempotency-Key | header | string | Any unique string up to 64 characters. Repeating it replays the first success instead of buying twice. |
curl -s -X POST https://smsgrab.com/api/dev/v1/activations \
-H "Authorization: Bearer $SMSGRAB_API_KEY" \
-H "Idempotency-Key: order-7f3c2a" \
-H "Content-Type: application/json" \
-d '{"service":"whatsapp","country":"indonesia","max_price_minor":20}'
{ "id": "5c1d0c2e-9f3a-4b7e-8a61-2f0c9d4e7b10", "compat_id": 100000123, "status": "WAITING_SMS",
"service_id": "whatsapp", "service_name": "WhatsApp", "country_id": "indonesia", "country_name": "Indonesia",
"country_iso_code": "ID", "phone_number": "+6281234567890", "price_minor": 17, "currency": "USD",
"created_at": "2026-09-27T10:05:00.000Z", "expires_at": "2026-09-27T10:25:00.000Z", "finished_at": null,
"refunded": false, "refund_minor": 0, "sms": [], "api_key_id": "0b8e2a41-7c1d-4e5f-9a3b-6c7d8e9f0a1b" }
GET
/activations
آپ کی ایکٹیویشنز کی فہرست۔ اجازت: read
| Parameter | In | Type | Description |
|---|---|---|---|
status | query | string | active (waiting or code received) or finished. |
service | query | string | Only this service id. |
country | query | string | Only this country id. |
from, to | query | date-time | Only activations bought in this time window (ISO 8601). |
source | query | string | api (default, bought through the developer API or the compatible endpoint) or all (the whole account). |
limit | query | integer | 1 to 100, default 50. |
cursor | query | string | next_cursor of the previous page. |
curl -s "https://smsgrab.com/api/dev/v1/activations?status=active&limit=20" \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "items": [ { "id": "5c1d0c2e-9f3a-4b7e-8a61-2f0c9d4e7b10", "status": "CODE_RECEIVED", "phone_number": "+6281234567890" } ], "next_cursor": "MTc5NDI1" }
GET
/activations/{id}
ایک ایکٹیویشن، اختیاری long polling کے ساتھ۔ اجازت: read
| Parameter | In | Type | Description |
|---|---|---|---|
id required | path | string | The activation UUID or its numeric compat_id. |
wait | query | integer | 0 to 30 seconds. The request is held until the activation changes (a new SMS or status) or the time runs out. At most 20 held requests per key. |
curl -s "https://smsgrab.com/api/dev/v1/activations/100000123?wait=25" \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "id": "5c1d0c2e-9f3a-4b7e-8a61-2f0c9d4e7b10", "compat_id": 100000123, "status": "CODE_RECEIVED",
"phone_number": "+6281234567890", "price_minor": 17, "currency": "USD",
"sms": [ { "code": "482913", "text": "Your WhatsApp code is 482-913", "sender": "WhatsApp",
"received_at": "2026-09-27T10:06:11.870Z" } ] }
POST
/activations/{id}/cancel
کوڈ آنے سے پہلے منسوخ کریں اور ریفنڈ لیں۔ اجازت: purchase
| Parameter | In | Type | Description |
|---|---|---|---|
id required | path | string | The activation UUID or compat_id. Accepted only while waiting for the first SMS; the charge is refunded. |
curl -s -X POST https://smsgrab.com/api/dev/v1/activations/100000123/cancel \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "id": "5c1d0c2e-9f3a-4b7e-8a61-2f0c9d4e7b10", "status": "CANCELLED", "refunded": true, "refund_minor": 17 }
POST
/activations/{id}/finish
کوڈ کے بعد ایکٹیویشن مکمل کریں۔ اجازت: purchase
| Parameter | In | Type | Description |
|---|---|---|---|
id required | path | string | The activation UUID or compat_id. Accepted once a code has arrived. |
curl -s -X POST https://smsgrab.com/api/dev/v1/activations/100000123/finish \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "id": "5c1d0c2e-9f3a-4b7e-8a61-2f0c9d4e7b10", "status": "COMPLETED", "finished_at": "2026-09-27T10:07:02.113Z" }
GET
/usage
وقت کے ساتھ درخواستیں، خریداریاں اور خرچ۔ اجازت: read
| Parameter | In | Type | Description |
|---|---|---|---|
from, to | query | date | YYYY-MM-DD, at most 366 days apart. Default: the last 30 days. |
granularity | query | string | day (default) or month. |
key | query | string | current (default, the calling key) or all (every key of the account). |
curl -s "https://smsgrab.com/api/dev/v1/usage?granularity=day&key=all" \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
{ "currency": "USD", "from": "2026-09-01", "to": "2026-09-27", "granularity": "day",
"totals": { "requests": 48211, "errors": 120, "rate_limited": 3, "purchases": 1804, "spend_minor": 31211,
"refunds_minor": 2410, "net_minor": 28801 },
"series": [ { "date": "2026-09-27", "requests": 2103, "errors": 4, "rate_limited": 0, "purchases": 88,
"spend_minor": 1496, "refunds_minor": 102, "net_minor": 1394 } ],
"by_key": [ { "key_id": "0b8e2a41-7c1d-4e5f-9a3b-6c7d8e9f0a1b", "name": "production bot", "requests": 48211, "net_minor": 28801 } ] }
ایکٹیویشن لائف سائیکل
ایکٹیویشن خریداری سے کوڈ تک کئی حالتوں سے گزرتی ہے۔
WAITING_SMSCODE_RECEIVEDCOMPLETED
WAITING_SMS: the number is ready and waiting for the first message. It can be cancelled (refunded).CODE_RECEIVED: at least one SMS arrived;smsholds every message withcode,text,senderandreceived_at. More messages may follow.COMPLETED: you finished it, or the code window ended after a code.CANCELLEDandEXPIRED: no code arrived. The charge is refunded andrefunded/refund_minorare set.
A number lives 20 minutes (expires_at). Cancel is accepted only while waiting (422 ACTIVATION_CODE_RECEIVED after a code; 422 ACTIVATION_CANCEL_NOT_YET when an early cancel is refused, retry after a minute). Finish is accepted once a code has arrived.
Long polling. GET /activations/{id}?wait=25 holds the request until the activation changes or the time runs out, then answers the current object. It replaces tight polling loops and costs one request per change.
ویب ہُکس
ویب ہُکس ایکٹیویشن ایونٹس ہوتے ہی آپ کے سرور پر بھیجتے ہیں۔
| Event | data |
|---|---|
activation.created | activation |
activation.code_received | activation (with every SMS so far) |
activation.completed | activation |
activation.cancelled | activation |
activation.expired | activation |
activation.refunded | activation |
balance.low | { balance_minor, currency, threshold_minor } |
webhook.test | { message } |
POST /your/endpoint HTTP/1.1
Content-Type: application/json
User-Agent: SmsGrab-Webhooks/1.0
SmsGrab-Event: activation.code_received
SmsGrab-Delivery: 9f7d6c1e-2b4a-4f0e-9c3d-1a2b3c4d5e6f
SmsGrab-Signature: t=1790503200,v1=3b9a0c5e7d2f41a8b6c9e0d1f2a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5
{
"id": "evt_4e1f9a0b2c3d",
"type": "activation.code_received",
"created_at": "2026-09-27T10:06:12.004Z",
"api_version": "2026-09-27",
"data": {
"activation": {
"id": "5c1d0c2e-9f3a-4b7e-8a61-2f0c9d4e7b10",
"status": "CODE_RECEIVED",
"sms": [{ "code": "482913", "text": "Your WhatsApp code is 482-913", "sender": "WhatsApp", "received_at": "2026-09-27T10:06:11.870Z" }]
}
}
}
- Success is any 2xx answer within 10 seconds. Redirects are never followed.
- Retries after 1 minute, 5 minutes, 15 minutes, 1 hour, 3 hours, 6 hours, 12 hours and 24 hours (±10 % jitter), then the delivery is marked
dead. A retry repeats the same eventid, so de-duplicate by it. - Order: events of one activation are delivered in order.
- 410 Gone switches the webhook off at once. After 50 failed attempts spanning at least 24 hours it is switched off and you get a notification.
- URLs must be
https://on port 443 or 1024–65535, use a host name, and resolve only to public addresses. Private, loopback, link-local and reserved ranges are refused. - Deliveries are kept 30 days. Send a test event, see every attempt and redeliver from your account.
دستخطوں کی تصدیق
ہر ڈیلیوری پر بھروسہ کرنے سے پہلے اس کی تصدیق کریں۔
SmsGrab-Signature: t=<unix seconds>,v1=<hex>. v1 is the lowercase hex HMAC-SHA256 of "<t>.<raw body>" with your webhook secret (whsec_…, shown once). Verify against the raw bytes before parsing JSON, reject a t more than 300 seconds from your clock, and compare in constant time.
PHP
<?php
function verifySmsGrabSignature(string $header, string $body, string $secret): bool
{
$parts = [];
foreach (explode(',', $header) as $part) {
[$name, $value] = array_pad(explode('=', $part, 2), 2, '');
$parts[trim($name)] = trim($value);
}
$timestamp = (int) ($parts['t'] ?? 0);
$expected = hash_hmac('sha256', $timestamp.'.'.$body, $secret);
return abs(time() - $timestamp) <= 300 && hash_equals($expected, $parts['v1'] ?? '');
}
$body = file_get_contents('php://input');
if (! verifySmsGrabSignature($_SERVER['HTTP_SMSGRAB_SIGNATURE'] ?? '', $body, getenv('SMSGRAB_WEBHOOK_SECRET'))) {
http_response_code(400);
exit;
}
$event = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
http_response_code(200);
Python
import hashlib
import hmac
import time
def verify(header: str, body: bytes, secret: str) -> bool:
parts = dict(part.split("=", 1) for part in header.split(",") if "=" in part)
timestamp = int(parts.get("t", "0"))
expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest()
return abs(time.time() - timestamp) <= 300 and hmac.compare_digest(expected, parts.get("v1", ""))
Node.js
import crypto from 'node:crypto';
export function verify(header, rawBody, secret) {
const parts = Object.fromEntries(header.split(',').map((part) => part.split('=', 2)));
const timestamp = Number(parts.t ?? 0);
const expected = crypto.createHmac('sha256', secret).update(`${timestamp}.`).update(rawBody).digest('hex');
const given = Buffer.from(parts.v1 ?? '', 'utf8');
return Math.abs(Date.now() / 1000 - timestamp) <= 300
&& given.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(expected, 'utf8'), given);
}
Go
package webhooks
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"strconv"
"strings"
"time"
)
func Verify(header string, body []byte, secret string) bool {
var timestamp int64
var signature string
for _, part := range strings.Split(header, ",") {
name, value, found := strings.Cut(strings.TrimSpace(part), "=")
if !found {
continue
}
switch name {
case "t":
timestamp, _ = strconv.ParseInt(value, 10, 64)
case "v1":
signature = value
}
}
age := time.Now().Unix() - timestamp
if age > 300 || age < -300 {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(strconv.FormatInt(timestamp, 10) + "."))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}
sms-activate موافقت
sms-activate پروٹوکول کے لیے بنے ٹولز ایک URL بدلنے پر کام کرتے ہیں۔
https://smsgrab.com/api/stubs/handler_api.php
GET or POST (form encoded) with api_key, action and the action's parameters. The key may also be sent as Authorization: Bearer. Answers are plain text (or JSON where shown) with HTTP 200 for every protocol answer, exactly like the original protocol; rate limiting answers HTTP 429 with TOO_MANY_REQUESTS. Amounts are decimal USD with two decimals (your developer price). A wrong, revoked or expired key answers BAD_KEY; an unknown action answers BAD_ACTION.
| action | Parameters | Success | Errors |
|---|---|---|---|
getBalance | – | ACCESS_BALANCE:12.34 | BAD_KEY |
getNumber | service, country, maxPrice? | ACCESS_NUMBER:100000123:6281234567890 | BAD_SERVICE, BAD_COUNTRY, NO_NUMBERS, NO_BALANCE, WRONG_MAX_PRICE:0.17, CHANNELS_LIMIT, BANNED:…, NO_ACCESS |
getNumberV2 | service, country, maxPrice? | {"activationId":"100000123","phoneNumber":"6281234567890","activationCost":"0.17",…} | same as getNumber |
setStatus | id, status (1, 3, 6, 8) | ACCESS_READY, ACCESS_RETRY_GET, ACCESS_ACTIVATION, ACCESS_CANCEL | NO_ACTIVATION, BAD_STATUS, EARLY_CANCEL_DENIED |
getStatus | id | STATUS_WAIT_CODE, STATUS_WAIT_RETRY:…, STATUS_OK:482913, STATUS_CANCEL | NO_ACTIVATION |
getStatusV2 | id | {"verificationType":0,"sms":{"code":"482913",…},"call":null} | NO_ACTIVATION |
getActiveActivations | – | {"status":"success","activeActivations":[…]} | {"status":"error","error":"NO_ACTIVATIONS"} |
getPrices | service?, country? | {"6":{"wa":{"cost":0.17,"count":30412}}} | BAD_SERVICE, BAD_COUNTRY |
getCountries | – | {"6":{"id":6,"eng":"Indonesia",…}} | – |
getServicesList | country? | {"status":"success","services":[{"code":"wa","name":"WhatsApp"}]} | – |
getNumbersStatus | country? | {"wa_0":"30412","tg_0":"1822"} | BAD_COUNTRY |
getTopCountriesByService | service | {"0":{"country":6,"count":30412,"price":0.17}} | BAD_SERVICE |
setStatus: 1 ready, 3 wait for another SMS, 6 finish, 8 cancel and refund. getActiveActivations reports 4 while waiting and 1 after a code. Times are UTC, yyyy-MM-dd HH:mm:ss. The activation id is the numeric compat_id, which the REST API accepts too.
Service codes
waWhatsApptgTelegramviViberigInstagramfbFacebookgoGoogletwX / TwitterdsDiscordlfTikTokamAmazonmmMicrosoftmbYahoooiTinderubUbertsPayPalnfNetflixwbWeChatvkVKontakteokOdnoklassnikidheBaykaShopeefuSnapchatmtSteambwSignaldrOpenAImeLINEktKakaoTalkotAny other
Country ids
0Russia6Indonesia4Philippines10Vietnam16United Kingdom22India36Canada43Germany73Brazil187USA
Our catalogue ids and ISO country codes are accepted as well, so every product is reachable. getCountries and getServicesList are the authoritative lists. Keys, scopes, allowlists, spend caps, limits, prices, refunds, webhooks and metering are the same as for the REST API.
curl
BASE="https://smsgrab.com/api/stubs/handler_api.php?api_key=$SMSGRAB_API_KEY"
curl -s "$BASE&action=getBalance"
curl -s "$BASE&action=getNumber&service=tg&country=6"
curl -s "$BASE&action=getStatus&id=100000123"
curl -s "$BASE&action=setStatus&id=100000123&status=6"
Python
import os
import requests
BASE = "https://smsgrab.com/api/stubs/handler_api.php"
KEY = os.environ["SMSGRAB_API_KEY"]
def action(name: str, **params) -> str:
response = requests.get(BASE, params={"api_key": KEY, "action": name, **params}, timeout=30)
response.raise_for_status()
return response.text
print(action("getBalance"))
answer = action("getNumber", service="tg", country=6)
if answer.startswith("ACCESS_NUMBER:"):
_, activation_id, number = answer.split(":")
print(activation_id, number)
مکمل مثالیں
پانچ زبانوں میں نمبر خریدیں، کوڈ کا انتظار کریں اور ایکٹیویشن مکمل کریں۔
curl
export SMSGRAB_API_KEY="sgk_your_key"
curl -s https://smsgrab.com/api/dev/v1/balance \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
curl -s -X POST https://smsgrab.com/api/dev/v1/activations \
-H "Authorization: Bearer $SMSGRAB_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"service":"telegram","country":"indonesia","max_price_minor":30}'
curl -s "https://smsgrab.com/api/dev/v1/activations/100000123?wait=25" \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
curl -s -X POST https://smsgrab.com/api/dev/v1/activations/100000123/finish \
-H "Authorization: Bearer $SMSGRAB_API_KEY"
Python
import os
import uuid
import requests
API = "https://smsgrab.com/api/dev/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['SMSGRAB_API_KEY']}"
def buy(service: str, country: str, max_price_minor: int) -> dict:
response = session.post(
f"{API}/activations",
json={"service": service, "country": country, "max_price_minor": max_price_minor},
headers={"Idempotency-Key": str(uuid.uuid4())},
timeout=30,
)
response.raise_for_status()
return response.json()
def wait_for_code(activation_id: str) -> str | None:
while True:
response = session.get(f"{API}/activations/{activation_id}", params={"wait": 25}, timeout=40)
response.raise_for_status()
activation = response.json()
if activation["sms"]:
return activation["sms"][-1]["code"]
if activation["status"] != "WAITING_SMS":
return None
activation = buy("telegram", "indonesia", 30)
print("Number:", activation["phone_number"])
code = wait_for_code(activation["id"])
if code:
print("Code:", code)
session.post(f"{API}/activations/{activation['id']}/finish", timeout=30).raise_for_status()
else:
print("No code arrived, the charge was refunded")
Node.js
import { randomUUID } from 'node:crypto';
const API = 'https://smsgrab.com/api/dev/v1';
async function call(path, { method = 'GET', body, idempotencyKey } = {}) {
const response = await fetch(`${API}${path}`, {
method,
headers: {
Authorization: `Bearer ${process.env.SMSGRAB_API_KEY}`,
...(body ? { 'Content-Type': 'application/json' } : {}),
...(idempotencyKey ? { 'Idempotency-Key': idempotencyKey } : {}),
},
body: body ? JSON.stringify(body) : undefined,
});
const data = response.status === 204 ? null : await response.json();
if (!response.ok) {
throw new Error(`${data?.code ?? response.status}: ${data?.message ?? ''}`);
}
return data;
}
let activation = await call('/activations', {
method: 'POST',
body: { service: 'telegram', country: 'indonesia', max_price_minor: 30 },
idempotencyKey: randomUUID(),
});
console.log('Number:', activation.phone_number);
while (activation.sms.length === 0 && activation.status === 'WAITING_SMS') {
activation = await call(`/activations/${activation.id}?wait=25`);
}
if (activation.sms.length > 0) {
console.log('Code:', activation.sms.at(-1).code);
await call(`/activations/${activation.id}/finish`, { method: 'POST' });
} else {
console.log('No code arrived, the charge was refunded');
}
PHP
<?php
require __DIR__.'/vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://smsgrab.com/api/dev/v1/',
'headers' => ['Authorization' => 'Bearer '.getenv('SMSGRAB_API_KEY')],
'timeout' => 40,
]);
$read = static fn ($response): array => json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR);
$activation = $read($client->post('activations', [
'json' => ['service' => 'telegram', 'country' => 'indonesia', 'max_price_minor' => 30],
'headers' => ['Idempotency-Key' => bin2hex(random_bytes(16))],
]));
echo 'Number: '.$activation['phone_number'].PHP_EOL;
while ($activation['sms'] === [] && $activation['status'] === 'WAITING_SMS') {
$activation = $read($client->get('activations/'.$activation['id'], ['query' => ['wait' => 25]]));
}
if ($activation['sms'] !== []) {
echo 'Code: '.$activation['sms'][array_key_last($activation['sms'])]['code'].PHP_EOL;
$client->post('activations/'.$activation['id'].'/finish');
} else {
echo 'No code arrived, the charge was refunded'.PHP_EOL;
}
Go
package main
import (
"bytes"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"os"
"time"
)
const api = "https://smsgrab.com/api/dev/v1"
type Activation struct {
ID string `json:"id"`
Status string `json:"status"`
PhoneNumber string `json:"phone_number"`
SMS []struct {
Code string `json:"code"`
} `json:"sms"`
}
var client = &http.Client{Timeout: 40 * time.Second}
func call(method, path string, body any, idempotencyKey string, out any) error {
var payload bytes.Buffer
if body != nil {
if err := json.NewEncoder(&payload).Encode(body); err != nil {
return err
}
}
request, err := http.NewRequest(method, api+path, &payload)
if err != nil {
return err
}
request.Header.Set("Authorization", "Bearer "+os.Getenv("SMSGRAB_API_KEY"))
if body != nil {
request.Header.Set("Content-Type", "application/json")
}
if idempotencyKey != "" {
request.Header.Set("Idempotency-Key", idempotencyKey)
}
response, err := client.Do(request)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode >= 300 {
var failure struct {
Code string `json:"code"`
Message string `json:"message"`
}
_ = json.NewDecoder(response.Body).Decode(&failure)
return fmt.Errorf("%s: %s", failure.Code, failure.Message)
}
if out == nil {
return nil
}
return json.NewDecoder(response.Body).Decode(out)
}
func idempotencyKey() string {
buffer := make([]byte, 16)
_, _ = rand.Read(buffer)
return hex.EncodeToString(buffer)
}
func main() {
var activation Activation
order := map[string]any{"service": "telegram", "country": "indonesia", "max_price_minor": 30}
if err := call("POST", "/activations", order, idempotencyKey(), &activation); err != nil {
panic(err)
}
fmt.Println("Number:", activation.PhoneNumber)
for len(activation.SMS) == 0 && activation.Status == "WAITING_SMS" {
if err := call("GET", "/activations/"+activation.ID+"?wait=25", nil, "", &activation); err != nil {
panic(err)
}
}
if len(activation.SMS) == 0 {
fmt.Println("No code arrived, the charge was refunded")
return
}
fmt.Println("Code:", activation.SMS[len(activation.SMS)-1].Code)
if err := call("POST", "/activations/"+activation.ID+"/finish", nil, "", nil); err != nil {
panic(err)
}
}
ورژنز اور تبدیلیاں
ہم API کو مستحکم رکھتے ہیں اور تبدیلیوں کا اعلان کرتے ہیں۔
- The current version is
2026-09-27; webhook payloads carry it inapi_version. - We add fields, events and error reasons without notice. Ignore fields and reasons you do not know.
- Breaking changes get a new path (
/dev/v2) and a long overlap. - The machine-readable description is part of the OpenAPI document at
https://smsgrab.com/api/v1/openapi.json.