SmsEmpire

L'API di SmsEmpire

Acquista verifiche SMS ed email dal tuo codice, addebitate sul tuo saldo, con uno sconto sul prezzo del sito.

  • Prezzo scontato su ogni acquisto effettuato via API
  • Webhook firmati appena arriva il codice — nessun ciclo di polling
  • Acquisti idempotenti: riprovare dopo un timeout è sicuro

Avvio rapido

Tutto sta sotto https://smsempire.com/api/v1. Autenticati inviando la tua chiave come bearer token. Solo lato server: questa API non invia header CORS, e una chiave usata da un browser è una chiave trapelata.

# 1. Create a key in your dashboard, then:
export SE_KEY="se_live_XXXXXXXXXXXX_..."

# 2. What will this cost?
curl -s https://smsempire.com/api/v1/prices?service=tg \
  -H "Authorization: Bearer $SE_KEY"

# 3. Buy a number. The Idempotency-Key is required.
curl -s https://smsempire.com/api/v1/activations \
  -H "Authorization: Bearer $SE_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"service":"tg","max_price":"0.35"}'

# 4. Collect the code (or let the webhook push it to you).
curl -s https://smsempire.com/api/v1/activations/90210 \
  -H "Authorization: Bearer $SE_KEY"

Convenzioni

  • Il denaro è una stringa decimale ("0.28000"), mai un numero JSON. Analizzalo con un tipo decimal, non con un float.
  • Le date sono in RFC3339 UTC.
  • Gli errori sono sempre {"error": {"code", "message", "request_id"}}. Ramifica su code; message può cambiare.
  • Cita il request_id in qualsiasi ticket di supporto: punta direttamente a quella richiesta nei nostri log.
  • I limiti tornano negli header X-RateLimit-*, e un 429 porta Retry-After.

Idempotenza (obbligatoria negli acquisti)

Ogni acquisto richiede un header Idempotency-Key — un UUID è ideale. Riutilizzarlo restituisce la risposta originale con Idempotent-Replay: true invece di comprare un secondo numero.

Esiste per una sola situazione: il tuo POST va in timeout e non sai se è andato a buon fine. Senza la chiave, riprovare compra due volte e non riprovare perde il numero già pagato. Con essa, riprova e basta.

Riusare una chiave con un corpo diverso dà 422, non una ripetizione silenziosa: quella combinazione è un bug lato tuo, e nasconderlo ti darebbe il risultato sbagliato.

max_price

Opzionale ma fortemente consigliato. Se ometti il paese ne proviamo diversi, in ordine di qualità e a prezzi diversi; max_price viene verificato su ogni candidato, quindi non ti si può mai addebitare più di quanto hai accettato. Se nessuno rientra, ricevi 409 PRICE_ABOVE_MAX con il prezzo attuale e non viene addebitato nulla.

Webhook

Registra un endpoint HTTPS nella tua dashboard. Inviamo un POST appena arriva un codice. La consegna è almeno una volta, con ritentativi scaglionati per circa due ore e mezza.

POST /your/webhook
X-SmsEmpire-Event: activation.code_received
X-SmsEmpire-Delivery: evt_01j8z...        <- deduplicate on this
X-SmsEmpire-Timestamp: 1765432991
X-SmsEmpire-Signature: v1=3a7f...

{
  "id": "evt_01j8z...",
  "type": "activation.code_received",
  "created_at": "2026-08-14T12:03:11Z",
  "data": {
    "activation_id": 90210,
    "service": "tg",
    "country": "2",
    "number": "+79991234567",
    "code": "483920",
    "status": "received",
    "price": "0.28000"
  }
}

Requisiti del tuo endpoint: HTTPS sulla porta 443, che risolva a un indirizzo pubblico. Non seguiamo i redirect e rifiutiamo destinazioni private, di loopback e di metadati cloud.

Verificare un webhook

La firma è HMAC-SHA256(segreto, "<timestamp>.<corpo grezzo>"), codificata in esadecimale. Quattro errori comuni, in ordine di frequenza:

  1. Firmare il JSON riserializzato invece dei byte grezzi. Riserializzare cambia i byte e la firma non combacerà mai.
  2. Ignorare il timestamp. Senza il controllo ±300 s, una consegna intercettata può esserti riproposta all'infinito.
  3. Confrontare con == invece che con un confronto a tempo costante.
  4. Non deduplicare su X-SmsEmpire-Delivery. Garantiamo almeno una volta, non esattamente una.
import hmac, hashlib, time
from flask import request, abort

SECRET = "whsec_..."          # from your dashboard
TOLERANCE = 300               # seconds

def verify(request):
    raw = request.get_data()                       # RAW bytes, before json parsing
    ts = request.headers.get("X-SmsEmpire-Timestamp", "")
    sig_header = request.headers.get("X-SmsEmpire-Signature", "")

    if abs(time.time() - int(ts)) > TOLERANCE:     # blocks replays
        abort(400)

    expected = hmac.new(SECRET.encode(),
                        f"{ts}.".encode() + raw,
                        hashlib.sha256).hexdigest()

    # More than one v1= during a secret rotation; any match is valid.
    sigs = [p.split("=", 1)[1] for p in sig_header.split(",") if p.startswith("v1=")]
    if not any(hmac.compare_digest(expected, s) for s in sigs):
        abort(400)
const crypto = require("crypto");

// express.raw({type: "application/json"}) — you need the RAW body, not the parsed one.
function verify(req, secret) {
  const ts = req.get("X-SmsEmpire-Timestamp") || "";
  const header = req.get("X-SmsEmpire-Signature") || "";

  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(Buffer.concat([Buffer.from(ts + "."), req.body]))
    .digest("hex");

  return header
    .split(",")
    .filter((p) => p.startsWith("v1="))
    .some((p) =>
      crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(p.slice(3))),
    );
}

Ruotando il segreto, il precedente resta valido per 24 ore ed entrambi firmano ogni consegna in quella finestra — così puoi rilasciare senza interruzioni.

Errori

codeCosa fare
INVALID_API_KEYChiave mancante, malformata o sconosciuta. Non riprovare.
KEY_REVOKED / KEY_EXPIREDCrea una nuova chiave. Non riprovare.
INSUFFICIENT_SCOPEQuesta chiave è stata creata senza accesso in scrittura.
API_ACCESS_DENIEDL'API è disattivata per questo account, o non hai ancora ricaricato.
API_DISABLEDL'API è temporaneamente disattivata per tutti. Riprova più tardi.
IDEMPOTENCY_KEY_REQUIREDInvia un header Idempotency-Key a ogni acquisto.
IDEMPOTENCY_KEY_REUSEDStessa chiave, corpo diverso. Correggi il tuo client.
IDEMPOTENCY_IN_PROGRESSIl primo tentativo è ancora in corso. Riprova tra un secondo.
PRICE_ABOVE_MAXIl prezzo ha superato il tuo max_price. Richiedi un nuovo preventivo e decidi.
INSUFFICIENT_BALANCERicarica il saldo. Non riprovare prima di averlo fatto.
COUNTRY_OUT_OF_STOCKAl momento non ci sono numeri. Riprova o scegli un altro paese.
PROVIDER_CAPACITYNon possiamo fornire quel paese al momento. Riprova più tardi.
CONCURRENCY_LIMITTroppe attivazioni in sospeso. Concludine o annullane alcune.
SPEND_LIMITRaggiunto il tetto di spesa orario. Riprova dopo la finestra.
RATE_LIMITEDRallenta. Rispetta Retry-After.
VALIDATION_ERRORIl corpo della richiesta è errato. Non riprovare senza modificarlo.

Ciclo di vita di un'attivazione

Un'attivazione è pending finché non arriva un codice (received), la marchi come usata (finished) o viene annullata. Annullare rimborsa esattamente quanto ti è stato addebitato.

Un'attivazione in sospeso scade 20 minuti dopo l'acquisto. La annulliamo sulla rete e ti rimborsiamo per intero non appena l'annullamento è confermato: non resti mai a pagare un numero che non ha ricevuto nulla.

C'è un periodo di grazia di circa 120 secondi prima di poter annullare un numero; annullare prima restituisce EARLY_CANCEL_DENIED con i secondi da attendere.

Limiti

Richieste in lettura, in scrittura e totale per utente hanno ciascuna un tetto al minuto, più tetti opzionali su attivazioni in sospeso, spesa oraria e attivazioni orarie. I tuoi valori attuali sono in GET /api/v1/me, dove null significa nessun limite. Se ti servono più alti apri un ticket: sono impostazioni per account, non valori fissi nel codice.

Riferimento completo degli endpoint

Documento OpenAPI 3.1 interattivo, sempre generato dal codice in esecuzione.

Apri il riferimento