API access
Authenticate with Authorization: Bearer <token>.
Your tokens
Log in to generate your API token. Everything else on this page is open to read.
Getting started
Base URL — https://smsportal.io/api/v2. Every call is HTTPS and answers JSON.
Authentication — send your token in the Authorization header as Bearer <token> on every request. Tokens start with tv_. Rotating a token invalidates the old one at once; session cookies are never accepted.
Requests — GET endpoints read the query string. POST endpoints read a JSON body (Content-Type: application/json); form-encoded bodies are accepted too.
Responses — always the same envelope: data holds the result, success is true or false, and summery carries a human-readable message (the spelling is part of the interface). Check success, not just the HTTP status.
Money — prices are US dollars and are charged from your wallet balance the moment an order is accepted. Orders that fail, are cancelled in time, or expire without a code are refunded automatically.
Polling or webhooks — you can poll the detail endpoints every few seconds until a code arrives, or register a webhook and get the code pushed to you the moment it lands. Webhooks are faster and cheaper for both sides.
Rate limit — 120 requests per minute per account, counted across all your tokens and endpoints in one-minute windows. Over the limit you get 429 with Retry-After and X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset headers. Polling one order every 2–3 seconds uses about 20–30. Need more? Contact support and we can raise it for your account — or use webhooks, which cost nothing against it.
Order limit — each account may hold a limited number of open (unanswered) orders at a time; the default is 5. Finish or cancel orders to free a slot.
Example request
Every call carries your token in an Authorization header. Bodies and replies are JSON.
curl -X POST https://smsportal.io/api/v2/tn101/reuse-number \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"order_id": 12345}'Response
Every endpoint answers with the same envelope: data holds the result, success says whether the call worked, and summery carries the message. Failures answer 401 (bad or missing token), 403 (account not active), 422 (validation, with the field errors in data) or 400.
{
"data": {
"order_id": 12346,
"reused_from_order_id": 12345,
"number": "12085551234",
"service": "Google",
"status": "Reserved",
"state": "CA",
"price": 0.35,
"reuse": true,
"till_expiration": "2026-01-31T12:20:00.000Z"
},
"success": true,
"summery": "Reserved"
}Errors and status codes
A failed call still answers with the envelope: success is false and summery says why, in words you can show a user.
- 200
It worked.
successistrue; readdata. - 400
The request was understood but could not be done — not enough balance, the service is out of stock or switched off, the order is no longer open.
summerysays which; nothing was charged. - 401
Unauthenticated— theAuthorizationheader is missing, malformed, or the token was revoked or rotated. - 402
Not enough balance (US Real SIM purchases and renewals).
- 403
The account is not active, or API access was switched off for it.
- 404
No such order, number or rental on this account.
- 409
Not possible yet or any more, e.g. cancelling inside the lock window —
data.retry_after_secondssays how long to wait. - 422
Validation failed.
datamaps each field to its messages:{ "order_id": ["The order_id field is required."] }. - 429
Too many requests. Wait the number of seconds in the
Retry-Afterheader (also indata.retry_after_seconds) and try again. - 502
The provider did not answer or refused. Safe to retry; you were not charged for an order that was not placed.
- 503
The API, or that product, is temporarily switched off.
- —
If
summeryreads "There is a technical issue on our side…", the problem is ours and we are already on it. Retrying will not help until it is fixed — contact support if it is urgent. You are never charged for an order that fails this way.
{
"data": {
"order_id": ["The order_id field is required."]
},
"success": false,
"summery": "Error"
}Endpoints
Open any endpoint for its parameters, a request in cURL, JavaScript, PHP and Python, and the reply it gives.
Account
GET/api/v2/profileAccount profile
Returns the account the API token belongs to. balance is the current wallet balance in USD as a number, and discount is your account discount in percent, already applied to every price the API quotes.
Query parameters
None.
Example request
curl "https://smsportal.io/api/v2/profile" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": {
"id": 4821,
"name": "Jane Doe",
"username": "janedoe",
"email": "[email protected]",
"contact_type": "telegram",
"contact_info": "@janedoe",
"discount": 0,
"balance": 24.5,
"email_verified": true,
"created_at": "2026-03-14T09:21:07.000Z"
},
"success": true,
"summery": "Profile info"
}GET/api/v2/account-detailsWallet balance
The lightest way to read your wallet: your username and current balance in USD as a number. Check it before placing orders rather than relying on an order failing.
Query parameters
None.
Example request
curl "https://smsportal.io/api/v2/account-details" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": {
"username": "janedoe",
"balance": 24.5
},
"success": true,
"summery": "Balance info"
}US Real SIM — /api/v2/real-sim
GET/api/v2/real-sim/get-servicesProducts, validity, prices and stock
Lists every US Real SIM option at your price (your account discount applied). Each validity window is its own product_id — pass that to request-number. stock_count is the exact number in stock (null when unknown) and stock is the same count as display text; available is false when a product is out of stock.
Query parameters
None.
Example request
curl "https://smsportal.io/api/v2/real-sim/get-services" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": [
{
"product_id": "118",
"service": "WhatsApp",
"country": "USA",
"validity": "7–15 days",
"min_days": 7,
"max_days": 15,
"price": "4.50",
"stock": "1,284",
"stock_count": 1284,
"available": true
},
{
"product_id": "119",
"service": "WhatsApp",
"country": "USA",
"validity": "30 days",
"min_days": 30,
"max_days": 30,
"price": "9.00",
"stock": "7",
"stock_count": 7,
"available": true
}
],
"success": true,
"summery": "Success"
}Errors
- 503
US Real SIM is switched off for maintenance (
"US Real SIM is currently unavailable").
GET/api/v2/real-sim/get-segmentsNumber segments and their stock
Lists the number segments a product can be bought from right now — the first digits of the number, country code included (1603 = +1 603…) — with the exact stock in each, most stocked first. Pass one as prefix to request-number to get a number from that segment. An empty data means segment choice is not offered for that product: buy without prefix. Stock moves quickly, so read it just before buying.
Query parameters
product_idstringrequiredA
product_idfromget-services.
Example request
curl "https://smsportal.io/api/v2/real-sim/get-segments" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": [
{ "prefix": "1231", "stock_count": 101 },
{ "prefix": "1603", "stock_count": 95 },
{ "prefix": "1937", "stock_count": 95 }
],
"success": true,
"summery": "Success"
}Errors
- 422
product_idis missing or not a positive integer. - 503
US Real SIM is switched off for maintenance (
"US Real SIM is currently unavailable").
POST/api/v2/real-sim/request-numberBuy numbers (queued)
Charges your balance for each number and queues the purchase, answering at once with order_ids and status: "pending". Poll get-order-status every 2–3 seconds, or listen for order.number_assigned / order.failed on your webhooks. A number that cannot be delivered is refunded automatically; a delivered number is not refundable. If your balance covers only some of the quantity, those are queued and summery says how many.
JSON body
product_idstringrequiredA
product_idfromget-services.quantityintegeroptionalHow many numbers to buy, 1–100. Defaults to 1.
prefixstringoptionalA number segment from
get-segments, such as1603. Every number in the order comes from that segment. Omit it for any segment (the widest stock).
Example request
curl -X POST "https://smsportal.io/api/v2/real-sim/request-number" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"product_id":"118","quantity":2,"prefix":"1603"}'Example response
{
"data": {
"order_ids": ["90412", "90413"],
"status": "pending",
"price": "4.50"
},
"success": true,
"summery": "Queued — poll get-order-status for the number"
}Errors
- 402
The balance does not cover even one number.
priceis per number. - 409
The product is unknown, no longer offered or out of stock, the
prefixsegment is sold out or has fewer numbers thanquantity, or you already hold the maximum number of numbers. - 422
product_idis missing or not a positive integer,quantityis not an integer from 1 to 100, orprefixis not 1–7 digits. - 503
US Real SIM is temporarily unavailable.
GET/api/v2/real-sim/get-order-statusPending, active or failed
Returns the orders you name, in the same shape as get-numbers. status is pending while the number is being obtained, then active with number, expires_at and a share_link, or failed with error (failed orders are refunded automatically). Ids that are not yours are left out of data rather than reported as errors.
Query parameters
order_idsstringrequiredComma-separated order ids from
request-number, 1 to 100. A singleorder_idis accepted instead.
Example request
curl "https://smsportal.io/api/v2/real-sim/get-order-status?order_ids=90412%2C90413" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": [
{
"order_id": "90412",
"status": "active",
"service": "WhatsApp",
"validity": "7–15 days",
"number": "12085551234",
"price": "4.50",
"code": null,
"last_message": null,
"message_count": 0,
"expires_at": "2026-10-21T14:05:12.000Z",
"share_link": "https://smsportal.io/n/Qm9oZ3hYc1R2a0xQ",
"error": null,
"renewal": null,
"auto_renew": false,
"created_at": "2026-10-10T14:05:08.000Z"
},
{
"order_id": "90413",
"status": "pending",
"service": "WhatsApp",
"validity": "7–15 days",
"number": null,
"price": "4.50",
"code": null,
"last_message": null,
"message_count": 0,
"expires_at": null,
"share_link": null,
"error": null,
"renewal": null,
"auto_renew": false,
"created_at": "2026-10-10T14:05:08.000Z"
}
],
"success": true,
"summery": "Success"
}Errors
- 422
order_idsis missing, holds a non-numeric id, or names more than 100 ids.
GET/api/v2/real-sim/get-numbersYour numbers
Your US Real SIM numbers, newest first, at most 500. By default only active and pending ones are returned; status=all adds expired and failed orders. code and last_message hold the latest SMS, and renewal is "pending" while a renewal requested through renew-number has not landed yet.
Query parameters
statusstringoptionalallto include expired and failed orders. Any other value (or none) returns active and pending numbers only.
Example request
curl "https://smsportal.io/api/v2/real-sim/get-numbers?status=active" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": [
{
"order_id": "90412",
"status": "active",
"service": "WhatsApp",
"validity": "7–15 days",
"number": "12085551234",
"price": "4.50",
"code": "482913",
"last_message": "Your WhatsApp code: 482-913. Don't share this code with others.",
"message_count": 1,
"expires_at": "2026-10-21T14:05:12.000Z",
"share_link": "https://smsportal.io/n/Qm9oZ3hYc1R2a0xQ",
"error": null,
"renewal": null,
"auto_renew": false,
"created_at": "2026-10-10T14:05:08.000Z"
}
],
"success": true,
"summery": "Success"
}GET/api/v2/real-sim/get-messagesMessages on a number
Every SMS received on one of your numbers, newest first (up to 200), with the verification code picked out into code when one is found. Messages also arrive as sms.received webhook events.
Query parameters
order_idintegerrequiredThe order id of the number, from
request-numberorget-numbers.
Example request
curl "https://smsportal.io/api/v2/real-sim/get-messages?order_id=90412" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": [
{
"sender": "32665",
"message": "Your WhatsApp code: 482-913. Don't share this code with others.",
"code": "482913",
"received_at": "2026-10-10T14:07:41.000Z"
}
],
"success": true,
"summery": "Success"
}Errors
- 404
No number with that
order_idbelongs to you, or the order failed. - 422
order_idis missing or not a number.
POST/api/v2/real-sim/renew-numberRenew numbers for another period
Charges the renewal price now for each active number (or one that expired in the last few days and can still be renewed) and extends it by the renewal period (days), usually within hours. get-numbers shows renewal: "pending" until it lands; a renewal not applied within the refund window (3 days by default) is refunded automatically. Numbers that are not yours, not active, or already have a renewal pending are counted in skipped; if the balance runs out part-way, the ones already paid for stand and summery says how many.
JSON body
order_idsstring | string[]requiredOrder ids to renew, comma-separated or as an array, 1 to 100. A single
order_idis accepted instead.
Example request
curl -X POST "https://smsportal.io/api/v2/real-sim/renew-number" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"order_ids":["90412","90413"]}'Example response
{
"data": {
"renewed": ["90412", "90413"],
"price": "9.50",
"days": 30,
"skipped": 0,
"status": "pending"
},
"success": true,
"summery": "Renewal requested"
}Errors
- 402
The balance does not cover even one renewal.
priceis per number. - 409
Nothing could be renewed: none of the numbers is active, a renewal is already pending, or renewal is not available right now.
- 422
order_idsis missing, holds a non-numeric id, or names more than 100 ids.
POST/api/v2/real-sim/set-auto-renewTurn auto-renew on or off
Turns auto-renew on or off. With it on, a number is renewed by itself about 30 hours before it expires, charged to your balance at the renew-number price; if the balance is too low you get an email and a notification beforehand, and the charge is retried hourly until the number expires. get-numbers shows auto_renew. Only active numbers can be switched on.
JSON body
order_idsstring | string[]requiredOrder ids, comma-separated or as an array, 1 to 100. A single
order_idis accepted instead.auto_renewbooleanrequiredtrueto turn it on,falseto turn it off.
Example request
curl -X POST "https://smsportal.io/api/v2/real-sim/set-auto-renew" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"order_ids":["90412"],"auto_renew":true}'Example response
{
"data": { "updated": 1, "auto_renew": true, "skipped": 0 },
"success": true,
"summery": "Updated"
}Errors
- 404
None of the order ids is an active number of yours (or, to turn it off, a number of yours).
- 422
order_idsorauto_renewis missing, or more than 100 ids are given.
Temp (s1) — /api/v2/tn101
GET/api/v2/tn101/get-servicesService list
Lists every service you can order, cheapest first. price is what you will pay, with your account discount already applied. Pass service_key to request-number. advanced_options_markup is the percentage added to the base price when you filter by state or area_codes.
Query parameters
None.
Example request
curl "https://smsportal.io/api/v2/tn101/get-services" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": {
"temporary": [
{
"service_id": "9b2f4c1e-6a3d-4e8f-b0a7-2d5c8e1f3a64",
"service_key": "tg",
"service": "Telegram",
"custom_app_name": "Telegram",
"country": "US",
"price": 0.45,
"has_premium": "1"
},
{
"service_id": "3e7a91d2-5b4c-4f0e-8c6d-71a2b9e4f05c",
"service_key": "wa",
"service": "WhatsApp",
"custom_app_name": "WhatsApp",
"country": "US",
"price": 0.75,
"has_premium": "0"
}
],
"advanced_options_markup": 20
},
"success": true,
"summery": "Services"
}GET/api/v2/tn101/us-statesUS states
The US states a number can be filtered to, keyed by the two-letter code you send as state to request-number. The list is static, so you can cache it.
Query parameters
None.
Example request
curl "https://smsportal.io/api/v2/tn101/us-states" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": {
"states": {
"AK": "Alaska (AK)",
"AL": "Alabama (AL)",
"AR": "Arkansas (AR)",
"AZ": "Arizona (AZ)",
"CA": "California (CA)",
"…": "…",
"WY": "Wyoming (WY)"
}
},
"success": true,
"summery": "States"
}POST/api/v2/tn101/request-numberOrder a number
Buys a number for one service. The price is charged before the number is requested. If no number can be supplied, the charge is refunded and the reply says you have not been charged. A state or area_codes filter adds advanced_options_markup percent to the price, and request_premium raises it by the bid percentage. Status is Reserved once a number is assigned, or Awaiting while a premium bid is still waiting for one (number is then null). Poll get-number-details with the returned order_id until pin arrives. The number expires at till_expiration. Each account can only have a limited number of open orders at once (5 by default).
JSON body
service_keystringrequiredThe service to order: its
service_keyorservice_idfromget-services(the display name also works, case-insensitively).service_idis accepted as an alias for this field.statestringoptionalTwo-letter US state code from
us-states, e.g.CA. Addsadvanced_options_markuppercent to the price.area_codesstringoptionalPreferred area codes, comma-separated, at most 100 characters (e.g.
208,212). Addsadvanced_options_markuppercent to the price.request_premiumintegeroptionalPriority bid in percent, from 10 to 2000, added to the service price. Only useful on services with
has_premium"1".markupis accepted as an alias.
Example request
curl -X POST "https://smsportal.io/api/v2/tn101/request-number" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"service_key":"wa","state":"CA"}'Example response
{
"data": {
"order_id": 918273,
"number": "12085551234",
"service": "WhatsApp",
"status": "Reserved",
"state": "CA",
"markup": null,
"price": 0.9,
"till_expiration": "2026-10-10T14:32:18.000Z"
},
"success": true,
"summery": "Requested"
}Errors
- 422
service_keyis missing or matches no orderable service,request_premiumis not a number between 10 and 2000,stateis not two letters, orarea_codesis longer than 100 characters. - 400
Your balance does not cover the price (
summerysays how much you need), the S1 server is disabled, the service is not available, or no number could be supplied. In every case you are not charged.
POST/api/v2/tn101/reuse-numberReuse a number
Re-reserves the same number from one of your earlier orders for the same service, so you can receive another code on it. Only an order with status Success can be reused. It creates and returns a new order: poll it by its own order_id. reused_from_order_id is the order you passed. A reuse is priced as a percentage of the first order you placed on that number, and may be free. If the number cannot be reserved again, the charge is refunded. Reuses count against your open-order limit like a new purchase.
JSON body
order_idintegerrequiredThe id of a completed order (status
Success) whose number you want again.
Example request
curl -X POST "https://smsportal.io/api/v2/tn101/reuse-number" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"order_id":918273}'Example response
{
"data": {
"order_id": 918402,
"reused_from_order_id": 918273,
"number": "12085551234",
"service": "WhatsApp",
"status": "Reserved",
"state": "CA",
"price": 0.25,
"reuse": true,
"till_expiration": "2026-10-10T15:05:41.000Z"
},
"success": true,
"summery": "Reserved"
}Errors
- 422
order_idis missing or is not an integer. - 400
The order does not exist or is not yours, it is not a
Successorder, the service is no longer available, your balance does not cover the reuse price, or the number can no longer be reserved. You are not charged.
GET/api/v2/tn101/get-number-detailsOrder detail
Reads one of your orders, including the code once it arrives: pin is the extracted code and message the full SMS text, both null until it is received. This is the endpoint to poll while you wait. It reads only our own records, so it is cheap, but a few seconds between calls is plenty. Stop when status becomes Success, or when it is Expired, Rejected or Failed.
Query parameters
order_idintegerrequiredThe
order_idreturned byrequest-numberorreuse-number.
Example request
curl "https://smsportal.io/api/v2/tn101/get-number-details?order_id=918273" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": {
"order_id": 918273,
"number": "12085551234",
"service": "WhatsApp",
"service_id": "3e7a91d2-5b4c-4f0e-8c6d-71a2b9e4f05c",
"status": "Success",
"state": "CA",
"pin": "482913",
"message": "Your WhatsApp code: 482-913. Don't share this code with others.",
"markup": null,
"price": 0.9,
"has_premium": "0",
"till_expiration": "2026-10-10T14:32:18.000Z",
"created_at": "2026-10-10T14:12:18.000Z"
},
"success": true,
"summery": "Order details"
}Errors
- 422
order_idis missing or is not an integer. - 404
No order with that id exists on your account.
GET/api/v2/tn101/get-reserved-listReserved numbers
Your open orders (Reserving, Awaiting or Reserved) that have not yet expired, newest first, at most 20. data is an array. An order placed a moment ago appears here straight away, so you can list right after ordering.
Query parameters
None.
Example request
curl "https://smsportal.io/api/v2/tn101/get-reserved-list" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": [
{
"order_id": 918402,
"number": "12085551234",
"service": "WhatsApp",
"service_id": "3e7a91d2-5b4c-4f0e-8c6d-71a2b9e4f05c",
"status": "Reserved",
"state": "CA",
"markup": null,
"message": null,
"pin": null,
"price": 0.25,
"has_premium": "0",
"till_expiration": "2026-10-10T15:05:41.000Z",
"created_at": "2026-10-10T14:45:41.000Z"
}
],
"success": true,
"summery": "Reserved list"
}GET/api/v2/tn101/get-order-historiesOrder history
Your finished orders (Success, Completed, Rejected, Expired and Failed), newest first, 20 per page. The orders are in data.data, and current_page, last_page, per_page, total, from and to describe the page. from and to are null on an empty page.
Query parameters
pageintegeroptionalPage number, starting at 1. Defaults to 1. Anything below 1 or not a number is read as 1.
Example request
curl "https://smsportal.io/api/v2/tn101/get-order-histories?page=1" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": {
"current_page": 1,
"last_page": 3,
"per_page": 20,
"total": 47,
"from": 1,
"to": 20,
"data": [
{
"id": 918273,
"number": "12085551234",
"status": "Success",
"service": "WhatsApp",
"service_id": "3e7a91d2-5b4c-4f0e-8c6d-71a2b9e4f05c",
"markup": null,
"pin": "482913",
"message": "Your WhatsApp code: 482-913. Don't share this code with others.",
"price": 0.9,
"has_premium": "0",
"created_at": "2026-10-10T14:12:18.000Z"
},
{
"id": 917955,
"number": "13055550187",
"status": "Rejected",
"service": "Telegram",
"service_id": "9b2f4c1e-6a3d-4e8f-b0a7-2d5c8e1f3a64",
"markup": null,
"pin": null,
"message": null,
"price": 0.45,
"has_premium": "1",
"created_at": "2026-10-09T18:40:02.000Z"
}
]
},
"success": true,
"summery": "Order Histories"
}POST/api/v2/tn101/reject-numberCancel and refund
Cancels an open order that has not received its SMS and refunds what it cost. A number that has already received a message is never refunded: we check with the provider before refunding, so cancelling right after a code lands is refused. refunded is false only when the order cost nothing. The reply shows the order as it now stands, with status Rejected.
JSON body
order_idintegerrequiredThe id of an open order (
AwaitingorReserved) to cancel.
Example request
curl -X POST "https://smsportal.io/api/v2/tn101/reject-number" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"order_id":918402}'Example response
{
"data": {
"order_id": 918402,
"number": "12085551234",
"service": "WhatsApp",
"status": "Rejected",
"state": "CA",
"markup": null,
"price": 0.25,
"has_premium": "0",
"refunded": true,
"till_expiration": "2026-10-10T15:05:41.000Z"
},
"success": true,
"summery": "Rejected and balance reverse"
}Errors
- 422
order_idis missing or is not an integer. - 404
No order with that id exists on your account, or it has not been assigned a number yet. Retry in a moment if you have only just placed it.
- 400
The number has already received its message (
That number already received its message), or the order is no longer open (That order is no longer open). Nothing is refunded. - 502
The cancellation could not be completed upstream. The order stays open and charged; try again.
Temp (s2) — /api/v2/tn102
GET/api/v2/tn102/get-servicesService list, carriers and surcharges
Every Temp (s2) service you can order, cheapest first, with price already discounted for your account. carrier_markup_percent and area_code_markup_percent are the surcharges added to that price when an order picks a carrier or area codes; they stack when both are chosen. Pass service_key to the ordering endpoints.
Query parameters
None.
Example request
curl "https://smsportal.io/api/v2/tn102/get-services" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": {
"carriers": ["at&t", "tmobile"],
"carrier_markup_percent": 20,
"area_code_markup_percent": 20,
"services": [
{
"service_id": "6f1c2a9e-3b4d-4e8f-9a17-2c5d8e0b4f63",
"service_key": "whatsapp",
"service": "WhatsApp",
"price": 0.5,
"stock": 412,
"multiple_sms": false,
"lifetime_minutes": 15
},
{
"service_id": "b2e47d10-8c3a-4f5b-9e62-71a0d4c9e8f2",
"service_key": "telegram",
"service": "Telegram",
"price": 0.75,
"stock": 128,
"multiple_sms": true,
"lifetime_minutes": 20
}
]
},
"success": true,
"summery": "Success"
}GET/api/v2/tn102/get-area-stockNumbers in stock per area code
Numbers in stock right now for one service, keyed by 3-digit area code; only codes with stock are listed. Use it to choose area_codes before calling request-number or request-multiple.
Query parameters
service_keystringrequiredThe
service_keyfromget-services. The service name orservice_idalso work.
Example request
curl "https://smsportal.io/api/v2/tn102/get-area-stock?service_key=whatsapp" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": {
"service_key": "whatsapp",
"stock": {
"212": 18,
"305": 7,
"415": 23
}
},
"success": true,
"summery": "Success"
}Errors
- 422
service_keyis missing or does not match an available service. - 503
Stock could not be fetched right now;
datais an empty array. Try again shortly.
POST/api/v2/tn102/request-numberOrder a number (carrier, area codes)
Buys a temporary number for one service and charges your balance at once. Choosing a carrier or area_codes each adds the surcharge percentage from get-services; a failed order is never charged. Poll get-number-details with the returned order_id until code is filled; a number that receives nothing is refunded automatically at till_expiration.
JSON body
service_keystringrequiredThe
service_keyfromget-services. The service name orservice_idalso work, andservice_idis accepted as the field name too.carrierstringoptionalat&tortmobile. Adds the carrier surcharge.keep_carrierbooleanoptionalWith
carrier: fail the order instead of falling back to another carrier.area_codesstringoptionalComma-separated 3-digit area codes, e.g.
"212,305"(at most 200 characters). Adds the area-code surcharge.lock_area_codebooleanoptionalWith
area_codes: fail the order instead of falling back to another area code.
Example request
curl -X POST "https://smsportal.io/api/v2/tn102/request-number" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"service_key":"whatsapp","carrier":"tmobile","area_codes":"212,305"}'Example response
{
"data": {
"order_id": 48213,
"number": "12125550147",
"service": "WhatsApp",
"status": "Reserved",
"code": null,
"message": null,
"carrier": "tmobile",
"area_codes": "212,305",
"price": 0.7,
"till_expiration": "2026-10-10T14:35:12.000Z",
"created_at": "2026-10-10T14:20:12.000Z"
},
"success": true,
"summery": "Requested"
}Errors
- 400
The order could not be placed: balance too low (
summerysays how much is needed), malformedarea_codes, open-order or daily limit reached, the service or server is unavailable, or no number could be obtained. Nothing is charged. - 422
service_keyis missing or invalid,carrieris notat&tortmobile, orarea_codesis longer than 200 characters.
POST/api/v2/tn102/request-multipleOne number for several services
One number for several services (2–7). Each service becomes its own order with its own charge, so codes, cancellation and refunds work per order. It is all or nothing: if the number cannot be obtained, nothing is charged, and any service the provider leaves off the number is refunded. carrier and area_codes add the same surcharges as request-number to every order.
JSON body
service_keysstring | string[]required2–7
service_keys fromget-services, as an array or a comma-separated string.carrierstringoptionalat&tortmobile. Adds the carrier surcharge to each order.keep_carrierbooleanoptionalWith
carrier: fail instead of falling back to another carrier.area_codesstringoptionalComma-separated 3-digit area codes, e.g.
"212,305". Entries that are not 3 digits are ignored. Adds the area-code surcharge to each order.lock_area_codebooleanoptionalWith
area_codes: fail instead of falling back to another area code.
Example request
curl -X POST "https://smsportal.io/api/v2/tn102/request-multiple" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"service_keys":["whatsapp","telegram"]}'Example response
{
"data": {
"number": "13055550198",
"total": 1.25,
"orders": [
{
"order_id": 48214,
"number": "13055550198",
"service": "WhatsApp",
"status": "Reserved",
"code": null,
"message": null,
"carrier": null,
"area_codes": null,
"price": 0.5,
"till_expiration": "2026-10-10T14:40:03.000Z",
"created_at": "2026-10-10T14:20:03.000Z"
},
{
"order_id": 48215,
"number": "13055550198",
"service": "Telegram",
"status": "Reserved",
"code": null,
"message": null,
"carrier": null,
"area_codes": null,
"price": 0.75,
"till_expiration": "2026-10-10T14:40:03.000Z",
"created_at": "2026-10-10T14:20:03.000Z"
}
]
},
"success": true,
"summery": "Requested"
}Errors
- 400
The order could not be placed: balance too low for all the services, more than the maximum number of services, open-order or daily limit reached, a service or the server is unavailable, or no number could be obtained. Nothing is charged.
- 422
Fewer than two
service_keys, any key that does not match an available service (listed in the message), orcarrieris notat&tortmobile.
POST/api/v2/tn102/request-specific-numberAsk for a particular number
Asks for one particular US number for a service. It costs the service price plus the specific-number surcharge and is charged only when the number is obtained. Poll get-number-details as for any other order.
JSON body
numberstringrequiredA 10-digit US number, e.g.
2125550147. A leading1and punctuation are accepted.service_keystringrequiredThe
service_keyfromget-services. The service name orservice_idalso work.
Example request
curl -X POST "https://smsportal.io/api/v2/tn102/request-specific-number" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"number":"2125550147","service_key":"whatsapp"}'Example response
{
"data": {
"order_id": 48216,
"number": "12125550147",
"service": "WhatsApp",
"status": "Reserved",
"code": null,
"message": null,
"carrier": null,
"area_codes": null,
"price": 0.6,
"till_expiration": "2026-10-10T14:36:40.000Z",
"created_at": "2026-10-10T14:21:40.000Z"
},
"success": true,
"summery": "Requested"
}Errors
- 400
Not a valid 10-digit US number, the number is not available, balance too low, open-order or daily limit reached, or the service or server is unavailable. Nothing is charged.
- 422
numberorservice_keyis missing, orservice_keydoes not match an available service.
POST/api/v2/tn102/reuse-numberGet the same number again
Rents the number from one of your earlier orders again, for the same service, as a new order. Only an order whose number received its code (status Success or Completed) can be reused. The price is a percentage of the original order's price set by the site, and nothing is charged if the number cannot be rented again.
JSON body
order_idintegerrequiredThe earlier order whose number you want again.
Example request
curl -X POST "https://smsportal.io/api/v2/tn102/reuse-number" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"order_id":48213}'Example response
{
"data": {
"order_id": 48302,
"number": "12125550147",
"service": "WhatsApp",
"status": "Reserved",
"code": null,
"message": null,
"carrier": null,
"area_codes": null,
"price": 0.7,
"till_expiration": "2026-10-10T16:05:21.000Z",
"created_at": "2026-10-10T15:50:21.000Z"
},
"success": true,
"summery": "Requested"
}Errors
- 400
The order is not yours or cannot be reused, it never received a code, its service is no longer available, balance too low, or the number could not be rented again. Nothing is charged.
- 422
order_idis missing or not a number.
GET/api/v2/tn102/get-number-detailsOrder detail and code
One of your Temp (s2) orders. Poll it after ordering: code holds the verification code and message the SMS text once it arrives, after which status becomes Success. Accepts GET with a query string or POST with a body.
Query parameters
order_idintegerrequiredThe
order_idreturned when the number was ordered.
Example request
curl "https://smsportal.io/api/v2/tn102/get-number-details?order_id=48213" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": {
"order_id": 48213,
"number": "12125550147",
"service": "WhatsApp",
"status": "Success",
"code": "482913",
"message": "Your WhatsApp code: 482-913. Don't share this code with others.",
"carrier": "tmobile",
"area_codes": "212,305",
"price": 0.7,
"till_expiration": "2026-10-10T14:35:12.000Z",
"created_at": "2026-10-10T14:20:12.000Z"
},
"success": true,
"summery": "Success"
}Errors
- 404
No order with that
order_idbelongs to your account. - 422
order_idis missing or not a number.
GET/api/v2/tn102/get-reserved-listOpen orders
Your open Temp (s2) orders (status Reserving or Reserved), newest first, up to 500. data is an array of orders in the same shape as get-number-details.
Query parameters
None.
Example request
curl "https://smsportal.io/api/v2/tn102/get-reserved-list" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": [
{
"order_id": 48216,
"number": "12125550147",
"service": "WhatsApp",
"status": "Reserved",
"code": null,
"message": null,
"carrier": null,
"area_codes": null,
"price": 0.6,
"till_expiration": "2026-10-10T14:36:40.000Z",
"created_at": "2026-10-10T14:21:40.000Z"
}
],
"success": true,
"summery": "Success"
}GET/api/v2/tn102/get-order-historiesOrder history
All your Temp (s2) orders in every status, newest first, 50 per page. No total is returned: keep increasing page until orders comes back with fewer than per_page entries.
Query parameters
pageintegeroptionalPage number, starting at 1 (the default).
Example request
curl "https://smsportal.io/api/v2/tn102/get-order-histories?page=1" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": {
"page": 1,
"per_page": 50,
"orders": [
{
"order_id": 48216,
"number": "12125550147",
"service": "WhatsApp",
"status": "Reserved",
"code": null,
"message": null,
"carrier": null,
"area_codes": null,
"price": 0.6,
"till_expiration": "2026-10-10T14:36:40.000Z",
"created_at": "2026-10-10T14:21:40.000Z"
},
{
"order_id": 48190,
"number": "14155550123",
"service": "Telegram",
"status": "Completed",
"code": "71035",
"message": "Telegram code: 71035",
"carrier": "at&t",
"area_codes": null,
"price": 0.9,
"till_expiration": "2026-10-09T09:32:05.000Z",
"created_at": "2026-10-09T09:12:05.000Z"
}
]
},
"success": true,
"summery": "Success"
}POST/api/v2/tn102/reject-numberCancel and refund (after a short lock)
Cancels an open order that has not received a code and refunds its price to your balance. Cancelling is locked for a short window after purchase; until then the call answers 409 with data.retry_after_seconds. refunded is true when the charge was returned.
JSON body
order_idintegerrequiredThe open order to cancel.
Example request
curl -X POST "https://smsportal.io/api/v2/tn102/reject-number" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"order_id":48216}'Example response
{
"data": {
"order_id": 48216,
"number": "12125550147",
"service": "WhatsApp",
"status": "Rejected",
"code": null,
"message": null,
"carrier": null,
"area_codes": null,
"price": 0.6,
"till_expiration": "2026-10-10T14:36:40.000Z",
"created_at": "2026-10-10T14:21:40.000Z",
"refunded": true
},
"success": true,
"summery": "Rejected"
}Errors
- 400
The number already received its code, or the order is no longer open.
- 404
No order with that
order_idbelongs to your account. - 409
Too soon after purchase.
data.retry_after_secondssays how long to wait before trying again. - 422
order_idis missing or not a number. - 502
The cancellation could not be completed right now; nothing changed. Try again.
Rental — /api/v2/rental
GET/api/v2/rental/get-servicesRental services and period prices
Rental services and the periods each can be rented for, priced for your account. Pass service_key and a period to request-number. Asking for specific area codes adds area_code_markup_percent to the price.
Query parameters
None.
Example request
curl "https://smsportal.io/api/v2/rental/get-services" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": {
"area_code_markup_percent": 20,
"services": [
{
"service_key": "telegram",
"service": "Telegram",
"periods": [
{ "period": "1d", "label": "1 day", "price": 1.2 },
{ "period": "7d", "label": "7 days", "price": 5.5 },
{ "period": "30d", "label": "30 days", "price": 14 }
]
},
{
"service_key": "whatsapp",
"service": "WhatsApp",
"periods": [
{ "period": "3d", "label": "3 days", "price": 3.4 },
{ "period": "14d", "label": "14 days", "price": 9.8 }
]
}
]
},
"success": true,
"summery": "Success"
}POST/api/v2/rental/request-numberRent a number
Rents one number for one service for the chosen period, charged from your balance, and answers with the rental — rental_id and number included. If the number cannot be issued you are not charged. Messages are collected about once a minute; read them with get-messages, or listen for sms.received on your webhooks.
JSON body
service_keystringrequiredA
service_keyfromget-services(case-insensitive).periodstringrequiredOne of the periods
get-serviceslists for that service:1d,3d,7d,14dor30d.area_codesstringoptionalPreferred US area codes, three digits each, separated by commas or spaces (e.g.
"212,305"). Adds the area-code markup to the price.lock_area_codebooleanoptionalWith
area_codes: only accept a number from those area codes. Ignored withoutarea_codes.auto_renewbooleanoptionalRenew automatically from your balance shortly before the rental ends. Defaults to
false.
Example request
curl -X POST "https://smsportal.io/api/v2/rental/request-number" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"service_key":"telegram","period":"7d","area_codes":"212,305","lock_area_code":false,"auto_renew":true}'Example response
{
"data": {
"rental_id": 5521,
"number": "12125550187",
"service": "Telegram",
"service_key": "telegram",
"period": "7d",
"period_label": "7 days",
"status": "Active",
"price": 6.6,
"auto_renew": true,
"renew_count": 0,
"message_count": 0,
"last_code": null,
"last_message": null,
"till_expiration": "2026-10-17T09:30:00.000Z",
"created_at": "2026-10-10T09:30:00.000Z"
},
"success": true,
"summery": "Rented"
}Errors
- 400
The rental could not be placed: rentals are disabled, the offer is unavailable or out of stock, the balance is too low (
"You need $… for this rental."), you already hold 50 rentals, or no number could be issued. - 422
service_keyorperiodis missing, or that service is not offered for that period.
GET/api/v2/rental/get-rentalsYour rentals
Your rentals, newest first, at most 500. By default only active ones (and any still being set up) are returned; status=all adds expired, cancelled and failed rentals. last_code and last_message hold the latest SMS.
Query parameters
statusstringoptionalallto include expired, cancelled and failed rentals. Any other value (or none) returns active rentals only.
Example request
curl "https://smsportal.io/api/v2/rental/get-rentals?status=active" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": [
{
"rental_id": 5521,
"number": "12125550187",
"service": "Telegram",
"service_key": "telegram",
"period": "7d",
"period_label": "7 days",
"status": "Active",
"price": 6.6,
"auto_renew": true,
"renew_count": 0,
"message_count": 1,
"last_code": "71842",
"last_message": "Telegram code: 71842. Do not give this code to anyone.",
"till_expiration": "2026-10-17T09:30:00.000Z",
"created_at": "2026-10-10T09:30:00.000Z"
}
],
"success": true,
"summery": "Success"
}GET/api/v2/rental/get-rental-detailsOne rental with its messages
One of your rentals, in any status, with every message received on it (newest first, up to 200) under messages. Accepts GET with a query string or POST with a body.
Query parameters
rental_idintegerrequiredThe
rental_idfromrequest-numberorget-rentals.
Example request
curl "https://smsportal.io/api/v2/rental/get-rental-details?rental_id=5521" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": {
"rental_id": 5521,
"number": "12125550187",
"service": "Telegram",
"service_key": "telegram",
"period": "7d",
"period_label": "7 days",
"status": "Active",
"price": 6.6,
"auto_renew": true,
"renew_count": 0,
"message_count": 1,
"last_code": "71842",
"last_message": "Telegram code: 71842. Do not give this code to anyone.",
"till_expiration": "2026-10-17T09:30:00.000Z",
"created_at": "2026-10-10T09:30:00.000Z",
"messages": [
{
"sender": "Telegram",
"message": "Telegram code: 71842. Do not give this code to anyone.",
"code": "71842",
"received_at": "2026-10-10T09:41:16.000Z"
}
]
},
"success": true,
"summery": "Success"
}Errors
- 404
No rental with that
rental_idbelongs to you. - 422
rental_idis missing or not a number.
GET/api/v2/rental/get-messagesMessages on a rental
Every message received on one of your rentals, newest first (up to 200), with the verification code picked out into code when one is found. New messages are collected about once a minute. Accepts GET with a query string or POST with a body.
Query parameters
rental_idintegerrequiredThe
rental_idfromrequest-numberorget-rentals.
Example request
curl "https://smsportal.io/api/v2/rental/get-messages?rental_id=5521" \ -H "Authorization: Bearer YOUR_API_TOKEN"
Example response
{
"data": [
{
"sender": "Telegram",
"message": "Telegram code: 71842. Do not give this code to anyone.",
"code": "71842",
"received_at": "2026-10-10T09:41:16.000Z"
}
],
"success": true,
"summery": "Success"
}Errors
- 404
No rental with that
rental_idbelongs to you. - 422
rental_idis missing or not a number.
POST/api/v2/rental/renew-rentalRenew for another period
Extends an active rental by its period, charging the current price for that period (without the area-code markup) from your balance; charged is the amount taken. The new end is one period after the current end, or after now if that has passed. If the renewal does not go through you are not charged.
JSON body
rental_idintegerrequiredThe
rental_idof an active rental.
Example request
curl -X POST "https://smsportal.io/api/v2/rental/renew-rental" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"rental_id":5521}'Example response
{
"data": {
"rental_id": 5521,
"number": "12125550187",
"service": "Telegram",
"service_key": "telegram",
"period": "7d",
"period_label": "7 days",
"status": "Active",
"price": 6.6,
"auto_renew": true,
"renew_count": 1,
"message_count": 1,
"last_code": "71842",
"last_message": "Telegram code: 71842. Do not give this code to anyone.",
"till_expiration": "2026-10-24T09:30:00.000Z",
"created_at": "2026-10-10T09:30:00.000Z",
"charged": 5.5
},
"success": true,
"summery": "Renewed"
}Errors
- 400
The service or period is no longer offered, so the rental cannot be renewed.
- 402
The balance does not cover the renewal;
summerygives the price. - 404
No active rental with that
rental_idbelongs to you. - 409
A renewal for this rental is already in progress.
- 422
rental_idis missing or not a number. - 502
The renewal did not go through; you have not been charged.
POST/api/v2/rental/set-auto-renewSwitch auto-renew on or off
Turns auto-renew on or off for an active rental. With it on, the rental is renewed from your balance shortly before it ends (an hour by default); after repeated failed attempts (e.g. a low balance) it stops trying until you set it again, which resets the attempt count.
JSON body
rental_idintegerrequiredThe
rental_idof an active rental.auto_renewbooleanrequiredtrue(or1) to switch auto-renew on; any other value switches it off.
Example request
curl -X POST "https://smsportal.io/api/v2/rental/set-auto-renew" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"rental_id":5521,"auto_renew":false}'Example response
{
"data": {
"rental_id": 5521,
"number": "12125550187",
"service": "Telegram",
"service_key": "telegram",
"period": "7d",
"period_label": "7 days",
"status": "Active",
"price": 6.6,
"auto_renew": false,
"renew_count": 1,
"message_count": 1,
"last_code": "71842",
"last_message": "Telegram code: 71842. Do not give this code to anyone.",
"till_expiration": "2026-10-24T09:30:00.000Z",
"created_at": "2026-10-10T09:30:00.000Z"
},
"success": true,
"summery": "Updated"
}Errors
- 404
No active rental with that
rental_idbelongs to you. - 422
rental_idis missing or not a number, orauto_renewis missing.
POST/api/v2/rental/cancel-rentalCancel and refund (no messages yet)
Cancels an active rental and refunds its current period in full — only while no message has arrived on the number (checked again with the provider before cancelling). The reply is the rental with status: "Rejected" and refunded.
JSON body
rental_idintegerrequiredThe
rental_idof an active rental with no messages.
Example request
curl -X POST "https://smsportal.io/api/v2/rental/cancel-rental" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"rental_id":5521}'Example response
{
"data": {
"rental_id": 5521,
"number": "12125550187",
"service": "Telegram",
"service_key": "telegram",
"period": "7d",
"period_label": "7 days",
"status": "Rejected",
"price": 6.6,
"auto_renew": false,
"renew_count": 0,
"message_count": 0,
"last_code": null,
"last_message": null,
"till_expiration": "2026-10-17T09:30:00.000Z",
"created_at": "2026-10-10T09:30:00.000Z",
"refunded": true
},
"success": true,
"summery": "Cancelled"
}Errors
- 400
A message has already arrived on the number, or the rental can no longer be cancelled.
- 404
No active rental with that
rental_idbelongs to you. - 422
rental_idis missing or not a number. - 502
The cancellation could not be completed right now; try again.
US Real SIM, Temp (s2) and Rental
US Real SIM sells real-SIM numbers kept for days. Buying is queued, so a slow carrier never times out your request:
get-services— every option; each validity window has its ownproduct_id,priceandstock.get-segments—product_id; the number segments (first digits, e.g.1603) you can pick from, with stock.request-number—product_id, optionalquantity(1–100), optionalprefix(a segment fromget-segments). Charges at once and answers withorder_idsandstatus: "pending". 402 when the balance is too low.get-order-status—order_ids(comma-separated, up to 100).pendingwhile we get the number, thenactivewithnumber,expires_atand ashare_link, orfailedwitherror— a failed number is refunded automatically. Polling every 2–3 seconds is fine; most numbers are ready within seconds.get-numbers(status=active|all) andget-messages(order_id) — every SMS, newest first, withcodepicked out.renew-number—order_ids; charges the renewal price now and extends each active number by the renewal period, usually within hours.get-numbersshowsrenewal: "pending"until it lands; one not applied within 3 days is refunded automatically.- A delivered number is not refundable. Buy one first when trying a service.
- Webhooks:
order.number_assignedwhen the number is ready,sms.receivedfor every SMS,order.failed+order.refundedwhen it could not be delivered; these carry"provider": "us-real-sim".
Temp (s2) works like Temp (s1), with two extra choices on request-number:
service_key— required; theservice_keyfromtn102/get-services.carrier— optional,at&tortmobile.keep_carrier: truefails the order instead of falling back to another carrier.area_codes— optional, comma-separated, e.g."212,305";tn102/get-area-stock?service_key=…lists what is in stock.lock_area_code: truefails the order instead of falling back.- Each choice adds the surcharge percentage returned by
get-services. A failed order is never charged. request-multiple—service_keys(2–7, comma-separated or an array) plus the samecarrier/area_codesoptions: one number for all of them, one order per service. All or nothing — if one service cannot go on the number, none is charged.request-specific-number—number(10-digit US) andservice_key: that exact number, for the service price plus a surcharge. Nothing is charged when it is not available.reject-numberworks once a short lock after purchase has passed. Before that it answers 409 withdata.retry_after_seconds.- Poll
get-number-details;codeholds the verification code once it arrives. Unused numbers are refunded automatically at expiry.
Rental keeps one number for one service for a period:
request-number—service_keyandperiod(1d,3d,7d,14dor30d), both required; optionalarea_codes,lock_area_code,auto_renew. Answers withrental_id.get-messages/get-rental-details—rental_id; every message received, newest first, with the code picked out.renew-rental—rental_id; charges the current price for the same period and extends the end date. 402 when the balance is too low.set-auto-renew—rental_id,auto_renew(true/false); renews from the balance shortly before the end.cancel-rental—rental_id; refunds in full only while no message has arrived.
Order events for both reach your webhooks like any other order; rental events carry "provider": "s2-rental".
Webhooks
Instead of polling, let us call you. When something happens on your account — a number is assigned, an SMS arrives, an order is refunded, a deposit clears — we send an HTTPS POST with a signed JSON body to the URL you register. Webhooks cover orders placed on the website and through the API alike.
1. Set up an endpoint
- Open Webhooks in your account and add your URL. It must be public HTTPS — private and local addresses are refused, and redirects are not followed, so register the final URL.
- Pick the events you want, or leave all of them on.
- Copy the signing secret (it starts with
whsec_) into your server's configuration. You can reveal or rotate it at any time from the same page. - Press Send test event. It delivers a sample
sms.receivedwith"test": trueindataso you can check your handler end to end without buying a number. - Every delivery, its response code and the first part of your reply are listed under the endpoint, with a Redeliver button for any of them.
2. Events
order.createdAn order was accepted and charged. The number may not be known yet.
order.number_assignedA number is ready and waiting for its SMS — for US Real SIM, when a queued purchase completes.
sms.receivedAn SMS arrived.
pinholds the code we picked out,messagethe full text. Fires for every SMS on a Real SIM number or rental.order.cancelledThe order was cancelled (by you, or closed because the number became unavailable).
order.expiredThe order ran out of time. For temporary numbers this means no SMS arrived; it is followed by
order.refunded.order.failedA number could not be supplied. Followed by
order.refunded.order.refundedThe charge went back to your balance. Follows cancelled, expired or failed.
deposit.confirmedA deposit cleared and your balance was credited.
bonus.creditedA deposit or referral bonus was credited.
transfer.sentYou sent a balance transfer to another user.
transfer.receivedAnother user sent you a balance transfer.
A typical temporary number produces order.created → order.number_assigned → sms.received, or, when no code arrives, order.expired → order.refunded. Order events say which product they belong to in data.provider: us-real-sim (US Real SIM), s1 (Temporary Number), s2 (Temporary Number S2), s2-rental (Rental). data.order_id is the same id the API returns, so you can match an event to your own record.
3. The payload
Every delivery has the same envelope: a unique id, the event type, created_at, and data. Order events carry the order; wallet events carry the amount and your new balance. Amounts are decimal strings, so they never lose precision.
{
"id": "3f6c2a9e-8b1d-4f7a-9c2e-5d4b1a7e0f63",
"type": "sms.received",
"created_at": "2026-10-10T12:04:31.000Z",
"data": {
"order_id": "48211",
"provider": "s2",
"service": "WhatsApp",
"number": "12085551234",
"status": "Success",
"price": "0.32",
"pin": "482913",
"message": "Your WhatsApp code: 482-913. Don't share this code with others.",
"expires_at": "2026-10-10T12:20:00.000Z",
"created_at": "2026-10-10T12:00:02.000Z"
}
}4. Headers
X-Webhook-Signaturesha256=followed by the hex HMAC-SHA256 of{timestamp}.{raw body}, keyed with the endpoint secret.X-Webhook-TimestampUnix seconds when this attempt was signed. Reject anything more than five minutes old to stop replays.
X-Webhook-EventThe event type, e.g.
sms.received— the same astypein the body.X-Webhook-Event-IdUnique per event and identical on every retry of it. Store it and skip ids you have already handled.
X-Webhook-DeliveryOur id for this delivery; quote it when asking support about one.
X-Webhook-Attempt1on the first try, then2,3, … on retries.Content-Typeapplication/json. The User-Agent isSMSPortal-Webhooks/1.0.
5. Verify the signature
Anyone can POST to your URL, so check that each delivery really came from us before acting on it:
- Take the raw request body exactly as received. Do not parse and re-serialise it first — that changes the bytes and every signature will fail.
- Build the string
{X-Webhook-Timestamp}.{raw body}and compute its HMAC-SHA256 with your signing secret, hex-encoded, prefixed withsha256=. - Compare it to
X-Webhook-Signaturewith a constant-time comparison, and reject timestamps more than five minutes old.
import crypto from 'node:crypto'
import express from 'express'
const SECRET = process.env.SMSPORTAL_WEBHOOK_SECRET // whsec_…
const seen = new Set() // use your database in production
const app = express()
// The raw body, exactly as sent — the signature covers these bytes.
app.post('/smsportal/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('X-Webhook-Timestamp')
const signature = req.get('X-Webhook-Signature') ?? ''
const body = req.body.toString('utf8')
// 1. Reject stale deliveries (replays).
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return res.sendStatus(400)
// 2. Recompute the signature and compare in constant time.
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(`${timestamp}.${body}`).digest('hex')
const valid =
expected.length === signature.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
if (!valid) return res.sendStatus(401)
const event = JSON.parse(body)
// 3. Retries reuse the event id — handle each one once.
if (seen.has(event.id)) return res.sendStatus(200)
seen.add(event.id)
// 4. Answer fast; do slow work after responding.
res.sendStatus(200)
if (event.type === 'sms.received') {
console.log(`Code ${event.data.pin} for order ${event.data.order_id}`)
}
})
app.listen(3000)6. Responding, retries and duplicates
- Answer with any 2xx status within 10 seconds. Anything else — a 3xx, 4xx, 5xx, a timeout or a connection error — counts as a failure.
- A failed delivery is retried immediately, then after 1 min, 5 min, 15 min, 1 h, 3 h and 6 h — 7 attempts in all over roughly ten hours. After the last one it is marked failed; you can still redeliver it by hand.
- An endpoint that fails 20 times in a row is switched off automatically and the reason is shown on the Webhooks page. Fix the receiver, then switch it back on.
- Retries carry the same
X-Webhook-Event-Id(and bodyid). Record the ids you have processed and ignore repeats — the same event can arrive twice, for example if your server took the request but timed out before answering. - Events are delivered independently and can arrive out of order. Use
created_at, or fetch the order from the API, when order matters. - Do the slow work after you have answered: acknowledge first, then queue the job.
Legacy format
Endpoints created on the old site keep the body they always received: only sms.received, unsigned, in the shape below. Switch an endpoint to the current format from the Webhooks page to get every event and a signature — we recommend it.
{
"type": "temporary",
"data": {
"number": "12085551234",
"service": "WhatsApp",
"pin": "482913",
"message": "Your WhatsApp code: 482-913."
}
}