SmsEmpire

La API de SmsEmpire

Compra verificaciones SMS y email desde tu propio código, con cargo a tu saldo y con descuento sobre el precio de la web.

  • Precio con descuento en cada compra hecha por API
  • Webhooks firmados en cuanto llega el código — sin bucle de sondeo
  • Compras idempotentes: reintentar tras un timeout es seguro

Inicio rápido

Todo cuelga de https://smsempire.com/api/v1. Autentícate enviando tu clave como bearer token. Solo desde servidor: esta API no envía cabeceras CORS, y una clave usada desde un navegador es una clave filtrada.

# 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"

Convenciones

  • El dinero es una cadena decimal ("0.28000"), nunca un número JSON. Parséalo con un tipo decimal, no con un float.
  • Las fechas son RFC3339 en UTC.
  • Los errores siempre son {"error": {"code", "message", "request_id"}}. Decide según code; message puede cambiar.
  • Cita el request_id en cualquier ticket de soporte: apunta directamente a esa petición en nuestros registros.
  • Los límites vienen en las cabeceras X-RateLimit-*, y un 429 incluye Retry-After.

Idempotencia (obligatoria al comprar)

Toda compra necesita una cabecera Idempotency-Key — lo ideal es un UUID. Reutilizarla devuelve la respuesta original con Idempotent-Replay: true en vez de comprar un segundo número.

Existe por una sola situación: tu POST da timeout y no sabes si llegó a ejecutarse. Sin la clave, reintentar compra dos veces y no reintentar pierde el número que ya pagaste. Con ella, simplemente reintenta.

Reutilizar una clave con un cuerpo distinto devuelve 422, no una repetición silenciosa: esa combinación es un fallo de tu cliente, y taparlo te daría un resultado equivocado.

max_price

Opcional, pero muy recomendable. Si no indicas país probamos varios, por orden de calidad y a precios distintos; max_price se comprueba contra cada candidato, así que nunca se te puede cobrar más de lo que aceptaste. Si ninguno encaja recibes un 409 PRICE_ABOVE_MAX con el precio actual, y no se cobra nada.

Webhooks

Registra un endpoint HTTPS en tu panel. Enviamos un POST con el evento en cuanto llega el código. La entrega es al menos una vez, con reintentos escalonados durante unas dos horas y media.

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"
  }
}

Requisitos de tu endpoint: HTTPS en el puerto 443 y que resuelva a una dirección pública. No seguimos redirecciones y rechazamos destinos privados, de loopback o de metadatos de nube.

Verificar un webhook

La firma es HMAC-SHA256(secreto, "<timestamp>.<cuerpo crudo>") en hexadecimal. Cuatro cosas que se hacen mal, por orden de frecuencia:

  1. Firmar el JSON reserializado en vez de los bytes crudos. Reserializar cambia los bytes y la firma nunca cuadrará.
  2. Ignorar el timestamp. Sin la comprobación de ±300 s, una entrega capturada se te puede reenviar para siempre.
  3. Comparar con == en lugar de con una comparación en tiempo constante.
  4. No deduplicar por X-SmsEmpire-Delivery. Garantizamos al menos una vez, no exactamente 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))),
    );
}

Al rotar el secreto, el anterior sigue siendo válido 24 horas y ambos firman cada entrega durante esa ventana, así que puedes desplegar sin cortes.

Errores

codeQué hacer
INVALID_API_KEYClave ausente, mal formada o desconocida. No reintentes.
KEY_REVOKED / KEY_EXPIREDCrea una clave nueva. No reintentes.
INSUFFICIENT_SCOPEEsta clave se creó sin permiso de escritura.
API_ACCESS_DENIEDLa API está desactivada para esta cuenta, o aún no has recargado.
API_DISABLEDLa API está desactivada temporalmente para todos. Reintenta más tarde.
IDEMPOTENCY_KEY_REQUIREDEnvía la cabecera Idempotency-Key en cada compra.
IDEMPOTENCY_KEY_REUSEDMisma clave, cuerpo distinto. Corrige tu cliente.
IDEMPOTENCY_IN_PROGRESSEl primer intento sigue en curso. Reintenta en un segundo.
PRICE_ABOVE_MAXEl precio subió por encima de tu max_price. Vuelve a consultar y decide.
INSUFFICIENT_BALANCERecarga saldo. No reintentes hasta hacerlo.
COUNTRY_OUT_OF_STOCKAhora mismo no hay números. Reintenta o prueba otro país.
PROVIDER_CAPACITYNo podemos servir ese país temporalmente. Reintenta más tarde.
CONCURRENCY_LIMITDemasiadas activaciones pendientes. Finaliza o cancela algunas.
SPEND_LIMITAlcanzado el techo de gasto por hora. Reintenta pasada la ventana.
RATE_LIMITEDBaja el ritmo. Respeta Retry-After.
VALIDATION_ERROREl cuerpo de tu petición es incorrecto. No reintentes sin cambiarlo.

Ciclo de vida de una activación

Una activación está pending hasta que llega el código (received), la marcas como usada (finished) o se cancela. Cancelar devuelve exactamente lo que se te cobró.

Una activación pendiente caduca a los 20 minutos de la compra. La cancelamos en la red y te devolvemos el importe íntegro en cuanto esa cancelación queda confirmada: nunca te quedas pagando un número que no recibió nada.

Hay un periodo de gracia de unos 120 segundos antes de poder cancelar un número; cancelar antes devuelve EARLY_CANCEL_DENIED con los segundos que faltan.

Límites

Las peticiones de lectura, las de escritura y el total por usuario tienen cada una un techo por minuto, y existen topes opcionales de activaciones pendientes, gasto por hora y activaciones por hora. Tus valores actuales están en GET /api/v1/me, donde null significa sin límite. Si necesitas ampliarlos, abre un ticket: son ajustes por cuenta, no valores fijos en el código.

Referencia completa de endpoints

Documento OpenAPI 3.1 interactivo, generado siempre desde el código en ejecución.

Abrir la referencia