Pangkalahatang-ideya
Ibinebenta ng developer API ang parehong mga numero ng app mula sa parehong balanse, sa pamamagitan ng REST API at isang tugmang endpoint.
- 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.
Quickstart
Tatlong tawag ang magdadala sa iyo mula sa bagong key hanggang sa natanggap na code.
- Gumawa ng account na may na-verify na email at buksan ang developer area.
- Gumawa ng key na may access na “Basa at bili” at kopyahin ang secret nito.
- Tiyaking sapat ang balanse para sa numerong gusto mo, pagkatapos ay patakbuhin ang mga tawag sa ibaba.
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 Kumpletong halimbawa.
Authentication
Bawat request ay may dalang isa sa iyong mga API key.
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 |
Mga API key
Ginagawa, nililimitahan at nire-rotate ang mga key sa account mo.
| 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.
Presyo
Batay ang presyo mo sa wholesale na gastos ng bawat alok na may markup na bumababa ayon sa volume.
developer_price = max(ceil(wholesale × (100 + markup) / 100), minimum)
developer_price = min(developer_price, app_price)
| Gastos ng developer sa 30 araw | Markup sa wholesale | Halimbawang presyo |
|---|---|---|
| Hanggang $100.00 | +80% | $0.18 28% mas mura kaysa sa app |
| Mula $100.00 | +70% | $0.17 32% mas mura kaysa sa app |
| Mula $500.00 | +60% | $0.16 36% mas mura kaysa sa app |
| Mula $2,000.00 | +50% | $0.15 40% mas mura kaysa sa app |
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.
Mga rate limit
Pinapanatiling mabilis ng mga limitasyon ang platform para sa lahat. Karamihan ng integration ay hindi umaabot dito.
| 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.
Mga error
Nagbabalik ang mga nabigong tawag ng matatag na code at, kung makakatulong, mas tiyak na dahilan.
{
"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
Ang ligtas na muling pagsubok ng pagbili ay hindi kailanman bumibili nang dalawang beses.
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.
Mga endpoint
Lahat ng endpoint ay nasa ilalim ng base URL at nagbabalik ng JSON.
- GET
/meAng tumatawag na key, ang presyo at mga limitasyon mo. - GET
/balanceAng balanse ng account mo. - GET
/countriesMga bansang may available na numero. - GET
/servicesMga serbisyo, maaaring limitado sa isang bansa. - GET
/pricesPresyo ng developer at availability bawat alok. - POST
/activationsBumili ng numero. - GET
/activationsListahan ng iyong mga activation. - GET
/activations/{id}Isang activation, may opsyonal na long polling. - POST
/activations/{id}/cancelKanselahin bago dumating ang code at ma-refund. - POST
/activations/{id}/finishTapusin ang activation pagkatapos ng code. - GET
/usageMga request, pagbili at gastos sa paglipas ng panahon.
GET
/me
Ang tumatawag na key, ang presyo at mga limitasyon mo. pahintulot: 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
Ang balanse ng account mo. pahintulot: 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
Mga bansang may available na numero. pahintulot: 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
Mga serbisyo, maaaring limitado sa isang bansa. pahintulot: 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
Presyo ng developer at availability bawat alok. pahintulot: 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
Bumili ng numero. pahintulot: 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
Listahan ng iyong mga activation. pahintulot: 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}
Isang activation, may opsyonal na long polling. pahintulot: 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
Kanselahin bago dumating ang code at ma-refund. pahintulot: 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
Tapusin ang activation pagkatapos ng code. pahintulot: 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
Mga request, pagbili at gastos sa paglipas ng panahon. pahintulot: 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 } ] }
Lifecycle ng activation
Dumaraan ang activation sa ilang estado mula pagbili hanggang code.
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.
Mga webhook
Ipinapadala ng mga webhook ang mga activation event sa server mo sa sandaling mangyari ang mga ito.
| 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.
Pag-verify ng pirma
I-verify ang bawat delivery bago ito pagkatiwalaan.
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))
}
Compatibility sa sms-activate
Gumagana ang mga tool para sa sms-activate protocol matapos palitan ang isang 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)
Kumpletong halimbawa
Bumili ng numero, hintayin ang code at tapusin ang activation sa limang wika.
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)
}
}
Mga bersyon at pagbabago
Pinapanatili naming matatag ang API at ipinapaalam ang mga pagbabago.
- 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.