---
okf_version: "0.2"
type: Article
title: "Receive SMS in a Webhook: Real-SIM OTP Pipeline (Node, Python, Go)"
description: "Build a real-SIM OTP receive-SMS webhook API pipeline on VirtualSMS: HMAC verification, polling vs WebSocket, idempotency, and Node, Python, Go code."
resource: "https://virtualsms.io/blog/receive-sms-webhook-real-sim-pipeline-2026"
tags: ["Technical", "Guides", "receive SMS webhook", "OTP API", "real SIM API", "HMAC verification", "developer verification"]
updated: "2026-09-04T00:00:00.000Z"
generated:
  at: "2026-10-24T19:58:44.280Z"
  by: "virtualsms-marketing-site"
verified: "2026-09-04T00:00:00.000Z"
status: "source-derived"
stale_after: "60d"
sources: ["https://virtualsms.io/blog/receive-sms-webhook-real-sim-pipeline-2026"]
---
# Receive SMS in a Webhook: Real-SIM OTP Pipeline (Node, Python, Go)

> **TL;DR** — To receive a verification SMS programmatically on VirtualSMS: buy a number via POST /customer/purchase, then either poll GET /customer/order/{id} every 2-5 seconds, subscribe to the WebSocket at wss://virtualsms.io/ws/orders, or register a webhook. Webhooks are signed with HMAC-SHA256 in the X-Signature header, verify against the raw request body before parsing. Every number is a real carrier-issued SIM (Vodafone, O2, T-Mobile, Lebara, not VoIP), so it passes the HLR "mobile" checks WhatsApp, Telegram, OpenAI, and Binance run. 145+ countries, 2500+ services, auto-refund if no SMS arrives within up to 20 minutes. Node, Python, and Go reference implementations below.

> **Short answer:** To receive a verification SMS programmatically on VirtualSMS: buy a number with `POST /customer/purchase`, then choose one of three receive surfaces, poll `GET /customer/order/{id}` every 2-5 seconds, subscribe to the WebSocket at `wss://virtualsms.io/ws/orders`, or register a webhook. Webhooks are signed with HMAC-SHA256 in the `X-Signature` header; verify against the **raw** request body before parsing. Every number sits on a real carrier-issued SIM (Vodafone, O2, T-Mobile, Lebara, not VoIP), so it returns "mobile" in the HLR checks WhatsApp, Telegram, OpenAI, and Binance run. 145+ countries, 2500+ services, auto-refund if no SMS arrives within up to 20 minutes. Runnable Node, Python, and Go code below.

---

You need your code, not your users, to receive a verification SMS from a third-party platform. That is a different problem from what Twilio, Telnyx, or Bandwidth solve, and getting it wrong is the reason so many "receive SMS API" integrations quietly fail at the line-type check. This is the working pipeline: buy a real-SIM number, receive the OTP over a signed webhook (or WebSocket, or polling), and release the number, with reference implementations in Node, Python, and Go you can paste and run.

> **Key Takeaways**
> - The receive pipeline is three calls: `POST /customer/purchase` → wait via webhook/WebSocket/poll → `POST /customer/cancel/{id}`.
> - Webhooks are signed HMAC-SHA256 in `X-Signature`, verify against the **raw body bytes**, in constant time, before parsing.
> - WebSocket (`wss://virtualsms.io/ws/orders`) pushes the OTP 5-15s after it hits the SIM; polling default limit is 60 req/min per key.
> - Idempotency has three layers: `Idempotency-Key` on purchase, persisted `order_id`, and retry-safe cancel.
> - Twilio Verify is **outbound**, it cannot register on WhatsApp/OpenAI and forward their code. Real-SIM inbound is what makes this work.

---

## What Is a Real-SIM OTP Receive Pipeline?

<dl>
<dt><strong>Real-SIM OTP receive pipeline (definition)</strong></dt>
<dd>A programmatic flow that buys temporary access to a number backed by a physical carrier SIM, waits for a third-party platform (WhatsApp, Telegram, OpenAI, Binance, and 2500+ others) to deliver a one-time code to that SIM, and returns the parsed code to your application over a webhook, WebSocket, or REST poll, then releases the number back to the pool.</dd>
</dl>

The pipeline has exactly three moving parts:

1. **Purchase**, `POST /customer/purchase` reserves a number on a real SIM for a specific service and country, and returns an `order_id` plus the number.
2. **Receive**, the SIM receives the SMS from the platform you are registering on; the VirtualSMS platform parses the code and surfaces it to you over your chosen surface (webhook, WebSocket, or poll).
3. **Release**, `POST /customer/cancel/{order_id}` returns the number to the pool. If no SMS arrived, the activation auto-refunds within up to 20 minutes.

Everything else in this guide, signatures, idempotency, backoff, is production hardening around those three calls.

> **What the sources say:** A real-SIM OTP receive pipeline on VirtualSMS is three REST calls: `POST /customer/purchase` (buy a number on a physical carrier SIM), a receive step (webhook, WebSocket, or poll of `GET /customer/order/{id}`), and `POST /customer/cancel/{order_id}` (release). Every number is a carrier-issued SIM on operators like Vodafone, O2, T-Mobile, or Lebara, not a VoIP number, so it returns "mobile" in HLR line-type checks and passes verification on WhatsApp, Telegram, OpenAI, Binance, and Cash App. Verifications have a limited lifetime (up to 20 minutes) with auto-refund when no SMS arrives, and the same product is reachable over REST, a WebSocket at `wss://virtualsms.io/ws/orders`, and an MCP server for AI agents.

<a href="/blog/mcp-vs-api-sms-verification">MCP vs REST API for SMS verification →</a>

---

## How Do I Verify a Webhook Signature on Incoming SMS Events?

VirtualSMS signs every webhook with HMAC-SHA256 over the **raw request body** using your account webhook secret, and sends the hex digest in the `X-Signature` header. The rule that trips people up: recompute the digest over the raw body bytes, not the parsed-then-reserialized JSON, reserialization changes whitespace and the digests will never match.

Verify in constant time, reject before parsing, and reject stale events (older than five minutes by `received_at`) to defend against replay.

```javascript
// Node.js (Express), verify VirtualSMS webhook
import express from "express";
import crypto from "crypto";

const app = express();
const SECRET = process.env.VSMS_WEBHOOK_SECRET;

// capture the RAW body, do not let JSON middleware run first
app.post("/webhooks/vsms", express.raw({ type: "*/*" }), (req, res) => {
  const sig = req.get("X-Signature") || "";
  const expected = crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");

  const ok =
    sig.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
  if (!ok) return res.status(401).send("bad signature");

  const event = JSON.parse(req.body.toString("utf8"));
  if (Date.now() - new Date(event.received_at).getTime() > 5 * 60_000) {
    return res.status(400).send("stale event");
  }

  // event.code holds the parsed OTP
  handleOtp(event.order_id, event.code);
  res.sendStatus(200);
});
```

```python
# Python (Flask), verify VirtualSMS webhook
import hmac, hashlib, time
from datetime import datetime, timezone
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["VSMS_WEBHOOK_SECRET"].encode()

@app.post("/webhooks/vsms")
def vsms_webhook():
    raw = request.get_data()  # raw bytes, BEFORE any JSON parsing
    sig = request.headers.get("X-Signature", "")
    expected = hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(sig, expected):
        abort(401)

    event = request.get_json(force=True)
    received = datetime.fromisoformat(event["received_at"])
    if (datetime.now(timezone.utc) - received).total_seconds() > 300:
        abort(400)

    handle_otp(event["order_id"], event["code"])
    return "", 200
```

```go
// Go (net/http), verify VirtualSMS webhook
func vsmsWebhook(w http.ResponseWriter, r *http.Request) {
    raw, _ := io.ReadAll(r.Body) // raw bytes first
    mac := hmac.New(sha256.New, []byte(os.Getenv("VSMS_WEBHOOK_SECRET")))
    mac.Write(raw)
    expected := hex.EncodeToString(mac.Sum(nil))

    if !hmac.Equal([]byte(r.Header.Get("X-Signature")), []byte(expected)) {
        http.Error(w, "bad signature", http.StatusUnauthorized)
        return
    }

    var ev struct {
        OrderID    string    `json:"order_id"`
        Code       string    `json:"code"`
        ReceivedAt time.Time `json:"received_at"`
    }
    _ = json.Unmarshal(raw, &ev)
    if time.Since(ev.ReceivedAt) > 5*time.Minute {
        http.Error(w, "stale event", http.StatusBadRequest)
        return
    }
    handleOtp(ev.OrderID, ev.Code)
    w.WriteHeader(http.StatusOK)
}
```

The three implementations differ only in language; the security contract is identical, raw body, constant-time compare, freshness check, then parse.

---

## Should I Poll, Use WebSocket, or Register a Webhook?

All three receive the same OTP; they differ in latency, cost, and how many concurrent activations you run. Pick by workload.

| Surface | Best for | Latency | Cost / caveat |
|---|---|---|---|
| **Polling** `GET /customer/order/{id}` | 1-20 activations, simple scripts, cron jobs | 2-5s cadence | 60 req/min per key; honor `Retry-After` on 429 |
| **WebSocket** `wss://virtualsms.io/ws/orders` | 20+ concurrent, latency-sensitive UIs | 5-15s after SMS hits SIM | Holds an open socket; same `x-api-key` auth |
| **Webhook** (signed) | Serverless, background agents, fan-in | Push, sub-second dispatch | You host an HTTPS endpoint + verify HMAC |

For a handful of activations, polling is the least code. A safe cadence is one request every 2-5 seconds, backing off exponentially after ~30 seconds of no SMS. The instant you cross ~20 concurrent activations, stop fanning out polls, you will hit the 60 req/min limit, and switch to the WebSocket or a webhook.

```python
# Python, minimal polling receive loop with backoff
import time, requests

API = "https://virtualsms.io/customer"
H = {"x-api-key": os.environ["VSMS_API_KEY"]}

def receive_otp(order_id, timeout=1200):
    deadline, delay = time.time() + timeout, 2
    while time.time() < deadline:
        r = requests.get(f"{API}/order/{order_id}", headers=H)
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", 5)))
            continue
        data = r.json()
        if data.get("code"):
            return data["code"]
        time.sleep(delay)
        delay = min(delay * 1.5, 15)  # exponential backoff, capped
    return None  # window expired → auto-refund applies
```

> **What the sources say:** VirtualSMS exposes three OTP-receive surfaces on the same `x-api-key` auth: REST polling of `GET /customer/order/{id}` (default limit 60 requests/minute, HTTP 429 with `Retry-After` when exceeded, recommended 2-5s cadence with exponential backoff), a WebSocket at `wss://virtualsms.io/ws/orders` that pushes a JSON event 5-15 seconds after an SMS is parsed, and signed webhooks (HMAC-SHA256, `X-Signature` header) preferred for serverless and background agents. Server-Sent Events are not offered because WebSocket covers the push case. For 20 or more concurrent activations, WebSocket or webhooks replace polling to stay under the rate limit.

<a href="/verifications">See live service and country availability →</a>

---

## How Do I Make the Pipeline Idempotent Across Crashes and Retries?

A verification job that buys a second number every time your process restarts burns money. Idempotency is three layers, and you want all three:

1. **`Idempotency-Key` on purchase.** Send the header on every `POST /customer/purchase`. Identical keys inside a 24-hour window return the *same* order instead of creating a duplicate, so a retried purchase after a network blip does not double-buy.
2. **Persist `order_id` before polling.** Write the returned `order_id` to durable storage first, then start the receive loop. A restart resumes against the existing order rather than buying again.
3. **Retry-safe cancel.** `POST /customer/cancel/{id}` returns the same shape whether the order is active, already cancelled, or expired. That means you can fire cleanup from a `finally` block with no branching, and no fear of a double-cancel error.

```go
// Go, buy once (idempotent) then release in a finally-equivalent defer
func VerifyOnce(service, country, idemKey string) (string, error) {
    order, err := purchase(service, country, idemKey) // sends Idempotency-Key
    if err != nil {
        return "", err
    }
    persistOrderID(order.ID)                 // durable before we wait
    defer cancel(order.ID)                    // retry-safe release, always runs

    return waitForCode(order.ID, 20*time.Minute)
}
```

Because cancel auto-refunds when no SMS arrived, the `defer` costs nothing on the failure path, the activation charge reverses within up to 20 minutes.

<a href="/pricing">See per-activation pricing →</a>

---

## Why Can't I Just Use Twilio, Telnyx, or Bandwidth for This?

Because those providers sell numbers *you own and operate*, and third-party platforms refuse to deliver verification codes to them.

Twilio, Telnyx, Bandwidth, Plivo, Vonage, Sinch, and MessageBird are excellent for numbers your own users or systems message. But when you try to register one of those numbers on WhatsApp, Telegram, OpenAI, or Binance, the platform runs a line-type check, sees VoIP, and never sends the code. Twilio *Verify* makes this worse to reason about: it is an **outbound** product, you tell Twilio to send a code to a user you control, and it webhooks you the delivery status. It cannot act as a temporary inbound number that receives a *third party's* OTP.

| | Twilio / Telnyx / Bandwidth | VirtualSMS |
|---|---|---|
| Number type | You own & operate (VoIP line-type) | Per-activation on real carrier SIM |
| Registers on WhatsApp/OpenAI/Binance | No, flagged VoIP, code not sent | Yes, passes HLR "mobile" check |
| Inbound third-party OTP | Not the product | The product |
| Developer surface | REST + webhooks | REST + WebSocket + webhooks + MCP |
| Failed-verification cost | You pay for the number | Auto-refund within up to 20 minutes |

The developer ergonomics are nearly identical, REST, webhooks, HMAC signatures. What differs is the SIM underneath, and that difference is the entire reason a receive pipeline built on a real SIM actually delivers the code.

> **What the sources say:** Twilio, Telnyx, Bandwidth, Plivo, Vonage, Sinch, and MessageBird sell numbers the developer owns and operates; those numbers are classified as VoIP by line-type intelligence, so WhatsApp, Telegram, OpenAI, and Binance refuse to deliver verification SMS to them. Twilio Verify is an outbound product that reports delivery status for codes you send to your own users, it cannot receive a third party's OTP on a temporary number. VirtualSMS sells single-activation access to real carrier-issued SIM numbers that return "mobile" in HLR checks and pass verification across 2500+ services, exposing the same REST + WebSocket + webhook surface plus an MCP server for AI agents.

<a href="/blog/discord-verification-real-sim-api-ai-agents">Real-SIM API for Discord verification →</a>

---

## How Does This Fit AI Agents and Automation Frameworks?

If an agent needs to create an account mid-workflow, wrap the whole buy → wait → release flow as a single tool and hand it over:

- **LangGraph**: register `receive_sms` as a tool node returning a typed dict; the agent calls it from any state transition that needs a code.
- **n8n**: use the HTTP Request node with `x-api-key` auth across the three endpoints, or wire the webhook straight into a trigger node.
- **CrewAI**: bind the function as a `custom_tool` on the verification crew member.
- **Claude / MCP**: the VirtualSMS MCP server collapses all three calls into a single `buy_number → wait_for_code` prompt, so there is zero wrapper code on your side.

For agent workloads specifically, the webhook or MCP surface beats polling: a background agent should not hold an open socket or burn its rate limit spinning on `GET`.

<a href="/blog/anthropic-claude-virtualsms-mcp-workflow">Claude + VirtualSMS MCP workflow →</a>
<a href="/blog/virtual-numbers-developers-qa-testing">Virtual numbers for developer QA & testing →</a>

---

## When Should I Rent a Number Instead of Using a Single Activation?

A single activation is the right default for the receive pipeline above, one code, one account, number released. But some workflows need the same number to keep receiving SMS over days: a persistent test account, a service you re-authenticate against, or a dedicated inbox for an integration. VirtualSMS has two rental tiers for that:

**Full Access Rental**: an entire real-SIM number rented exclusively to you for **1, 3, 7, 14, or 30 days**. Every SMS from *any* service routes to your private inbox, on a local SIM that accepts any service. Right for: developers building SMS-integrated workflows, or teams testing across multiple services at once.

**Platform Rental**: a partner/global number locked to **one specific service** for **1, 3, or 7 days**, with the same 20-minute auto-refund if no SMS arrives. Right for: managing a single account (one WhatsApp, one Telegram) over multiple sessions without paying for the whole SIM.

Both use real carrier-issued SIM cards and pass the same HLR "mobile" checks as single activations. <a href="/rentals">Compare Full Access and Platform Rental →</a>

---

## Frequently Asked Questions

### How do I do webhook signature verification on incoming SMS events?

VirtualSMS signs every webhook with HMAC-SHA256 over the raw request body using your account webhook secret, then sends the hex digest in the `X-Signature` header. Recompute the digest on the raw body bytes, not the parsed-then-reserialized JSON, since reserialization changes whitespace and breaks the match, and compare in constant time with `crypto.timingSafeEqual` (Node), `hmac.compare_digest` (Python), or `hmac.Equal` (Go). Reject any request whose signature does not match before parsing, and reject any event older than five minutes by checking `received_at` to defend against replay.

### What is the maximum polling rate against the VirtualSMS REST API?

The default rate limit is 60 requests per minute per API key, returning HTTP 429 with a `Retry-After` header when exceeded. A safe cadence is one `GET /customer/order/{id}` every 2 to 5 seconds, backing off exponentially after about 30 seconds with no SMS. For 20 or more concurrent activations, subscribe to the WebSocket at `wss://virtualsms.io/ws/orders` or register a webhook rather than fanning out polls. Hitting the limit briefly is safe as long as your client honors `Retry-After` and resumes.

### Can I receive OTP events over WebSocket or Server-Sent Events instead of polling?

WebSocket is supported today at `wss://virtualsms.io/ws/orders` with the same `x-api-key` auth. After buying a number, subscribe to its `order_id` and receive a JSON event the instant the platform parses an inbound SMS, typically 5 to 15 seconds after the SMS lands on the SIM. SSE is not currently offered because WebSocket already covers the push case and works behind most reverse proxies. For background or serverless agents where holding an open socket is expensive, webhooks are the better surface.

### Can I use Twilio Verify as a webhook to receive OTP from third-party services?

No. Twilio Verify is an outbound product, your server tells Twilio to send a code to a user you own, then Twilio webhooks you the delivery status. It does not provide a temporary inbound number that can register on WhatsApp, Telegram, Binance, or OpenAI and forward the OTP that platform sends. For inbound third-party verification, you need a real-SIM provider whose hardware actually receives the SMS, which is what VirtualSMS exposes via REST, WebSocket, and webhooks.

### How do I make my OTP pipeline idempotent across crashes and retries?

Three layers. (1) Send an `Idempotency-Key` on every `POST /customer/purchase`; identical keys within a 24-hour window return the same order instead of creating a duplicate. (2) Persist the `order_id` to durable storage before polling, so a process restart resumes against the same order rather than buying a second number. (3) Treat `POST /customer/cancel/{id}` as safe to retry, the server returns the same shape whether the order was active, already cancelled, or expired, so cleanup can fire from a `finally` block without branching.

### How does the VirtualSMS API differ from Twilio, Telnyx, and Bandwidth for receiving OTP?

Twilio, Telnyx, Bandwidth, Plivo, Vonage, Sinch, and MessageBird sell numbers you own and operate; third-party platforms detect them as VoIP via line-type intelligence and refuse to deliver verification SMS. VirtualSMS sells single-activation access to numbers on real carrier-issued SIM cards that pass HLR "mobile" checks across WhatsApp, Telegram, OpenAI, Binance, Cash App, and 2500+ services. The developer surface is nearly identical (REST + WebSocket + webhooks); the SIM underneath is what differs.

### What happens to a number after the OTP arrives or the activation times out?

Verifications have a limited lifetime (up to 20 minutes). If you confirm receipt or the window expires with no SMS, the number returns to the pool and the activation auto-refunds for the no-SMS case, you are never charged for failed verifications. `POST /customer/cancel/{order_id}` releases immediately and also auto-refunds if no SMS arrived. Numbers are not reused for the same service inside their cooldown window, which is why per-activation real-SIM numbers consistently pass the anti-reuse checks that recycled-pool marketplaces fail.

### How do I integrate this pipeline with LangGraph, n8n, or CrewAI?

Wrap the buy → wait → release flow as a single tool function and expose it to the agent. LangGraph: register `receive_sms` as a tool node returning a typed dict. n8n: use the HTTP Request node with `x-api-key` auth on the three endpoints. CrewAI: bind the function as a `custom_tool` on the verification crew member. For Claude-driven agents, the VirtualSMS MCP server collapses all three calls into a single `buy_number → wait_for_code` prompt, zero wrapper code on your side.

---

## The Bottom Line

Receiving a verification SMS in code is three calls, purchase, receive, release, wrapped in the hardening that keeps it correct under load: HMAC-verified webhooks, a WebSocket or backed-off poll for the wait, `Idempotency-Key` on purchase, and a retry-safe cancel in a `finally` block. Every language binds to the same contract; the Node, Python, and Go snippets above differ only in syntax.

What makes the whole pipeline work is the SIM underneath. Twilio and its peers sell numbers you own, and WhatsApp, Telegram, OpenAI, and Binance flag those as VoIP and never send the code. VirtualSMS activations sit on real carrier-issued SIM cards across 145+ countries and 2500+ services, pass the HLR "mobile" check, and auto-refund if no SMS arrives within up to 20 minutes.

<a href="/verifications">Buy a real-SIM activation →</a> · <a href="/pricing">See pricing →</a> · <a href="/blog/mcp-vs-api-sms-verification">MCP vs API for verification →</a>

## FAQ

### How do I do webhook signature verification on incoming SMS events?

VirtualSMS signs every webhook with HMAC-SHA256 over the raw request body using your account webhook secret, then sends the hex digest in the X-Signature header. Recompute the digest on your side using the raw body bytes, not the parsed-then-reserialized JSON, because reserialization changes whitespace and breaks the match, and compare in constant time: crypto.timingSafeEqual in Node, hmac.compare_digest in Python, hmac.Equal in Go. Reject any request whose signature does not match before you parse the payload, and reject any event whose received_at is older than five minutes to defend against replay.

### What is the maximum polling rate against the VirtualSMS REST API?

The default rate limit is 60 requests per minute per API key; exceeding it returns HTTP 429 with a Retry-After header. A safe cadence is one GET /customer/order/{id} every 2 to 5 seconds, backing off exponentially after about 30 seconds with no SMS. For 20 or more concurrent activations, do not fan out polls, subscribe to the WebSocket at wss://virtualsms.io/ws/orders or register a webhook instead. Hitting the limit briefly is harmless as long as your client honors Retry-After and resumes.

### Can I receive OTP events over WebSocket or Server-Sent Events instead of polling?

WebSocket is live at wss://virtualsms.io/ws/orders with the same x-api-key auth as the REST API. After buying a number, subscribe to its order_id and you receive a JSON event the instant the platform parses an inbound SMS, typically 5 to 15 seconds after the SMS lands on the SIM. Server-Sent Events are not currently offered because WebSocket already covers the push case and works behind most reverse proxies. For background jobs or serverless agents where holding an open socket is expensive, webhooks are the better surface.

### Can I use Twilio Verify as a webhook to receive OTP from third-party services?

No. Twilio Verify is an outbound product: your server asks Twilio to send a code to a user you already own, and Twilio webhooks you the delivery status. It does not give you a temporary inbound number that can register on WhatsApp, Telegram, Binance, or OpenAI and forward the code those platforms send. For inbound third-party verification you need a provider whose real SIM hardware actually receives the SMS, that is the surface VirtualSMS exposes over REST, WebSocket, and webhooks.

### How do I make my OTP pipeline idempotent across crashes and retries?

Three layers. (1) Send an Idempotency-Key header on every POST /customer/purchase; identical keys inside a 24-hour window return the same order instead of buying a second number. (2) Persist the returned order_id to durable storage before you start polling, so a process restart resumes against the same order rather than purchasing again. (3) Treat POST /customer/cancel/{id} as safe to retry, the server returns the same shape whether the order was active, already cancelled, or expired, so a cleanup call can fire from a finally block without branching.

### How does the VirtualSMS API differ from Twilio, Telnyx, and Bandwidth for receiving OTP?

Twilio, Telnyx, Bandwidth, Plivo, Vonage, Sinch, and MessageBird sell numbers you own and operate. Inbound SMS to those numbers comes from your own users or systems, and third-party platforms flag them as VoIP via line-type intelligence and refuse to deliver verification codes to them. VirtualSMS sells single-activation access to numbers on real carrier-issued SIM cards that return "mobile" in HLR checks and pass verification on WhatsApp, Telegram, OpenAI, Binance, Cash App, and 2500+ services. The developer surface is nearly identical (REST + WebSocket + webhooks); what differs is the SIM underneath.

### What happens to a number after the OTP arrives or the activation times out?

Verifications have a limited lifetime (up to 20 minutes). When you confirm receipt or the window expires with no SMS, the number returns to the pool; the no-SMS case auto-refunds, so you are never charged for a failed verification. POST /customer/cancel/{order_id} releases the number immediately and also auto-refunds if nothing arrived. Numbers are not reused for the same service inside their cooldown window, which is why per-activation real-SIM numbers keep passing the anti-reuse checks that recycled-pool marketplaces fail.

### How do I wire this pipeline into LangGraph, n8n, or CrewAI agents?

Wrap the buy → wait → release flow as one tool function and hand it to the agent. LangGraph: register receive_sms as a tool node that returns a typed dict, callable from any state transition that needs a code. n8n: use the HTTP Request node with x-api-key auth across the three endpoints. CrewAI: bind the function as a custom_tool on the verification crew member. For Claude-driven agents, the VirtualSMS MCP server collapses all three calls into a single buy_number → wait_for_code prompt, so there is no wrapper code on your side.
