L'API SmsEmpire
Achetez des vérifications SMS et e-mail depuis votre propre code, débitées de votre solde, avec une remise sur le prix du site.
- →Tarif remisé sur chaque achat effectué via l'API
- →Webhooks signés dès l'arrivée du code — pas de boucle d'interrogation
- →Achats idempotents : réessayer après un timeout est sans risque
Démarrage rapide
Tout se trouve sous https://smsempire.com/api/v1. Authentifiez-vous en envoyant votre clé comme bearer token. Côté serveur uniquement : cette API n'envoie aucun en-tête CORS, et une clé utilisée depuis un navigateur est une clé divulguée.
# 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"Conventions
- L'argent est une chaîne décimale ("0.28000"), jamais un nombre JSON. Analysez-la avec un type décimal, pas un float.
- Les horodatages sont en RFC3339 UTC.
- Les erreurs sont toujours {"error": {"code", "message", "request_id"}}. Branchez sur code ; message peut changer.
- Citez le request_id dans tout ticket de support : il pointe directement sur la requête dans nos journaux.
- Les limites reviennent dans les en-têtes X-RateLimit-*, et un 429 porte Retry-After.
Idempotence (obligatoire à l'achat)
Chaque achat exige un en-tête Idempotency-Key — un UUID est idéal. La réutiliser renvoie la réponse d'origine avec Idempotent-Replay: true au lieu d'acheter un second numéro.
Cela existe pour une seule situation : votre POST expire et vous ignorez s'il a abouti. Sans la clé, réessayer achète deux fois et ne pas réessayer perd le numéro déjà payé. Avec elle, réessayez simplement.
Réutiliser une clé avec un corps différent renvoie 422, pas une répétition silencieuse : cette combinaison est un bug de votre côté, et la masquer vous donnerait un résultat faux.
max_price
Facultatif mais vivement recommandé. Si vous omettez le pays, nous en essayons plusieurs, par ordre de qualité et à des prix différents ; max_price est vérifié sur chaque candidat, vous ne pouvez donc jamais être facturé plus que ce que vous avez accepté. Si rien ne convient, vous obtenez 409 PRICE_ABOVE_MAX avec le prix actuel, et rien n'est débité.
Webhooks
Enregistrez un endpoint HTTPS dans votre tableau de bord. Nous envoyons un POST dès qu'un code arrive. La livraison est au moins une fois, réessayée avec un délai croissant sur environ deux heures et demie.
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"
}
}Exigences pour votre endpoint : HTTPS sur le port 443, résolvant vers une adresse publique. Nous ne suivons pas les redirections et refusons les destinations privées, loopback et métadonnées cloud.
Vérifier un webhook
La signature est HMAC-SHA256(secret, "<timestamp>.<corps brut>"), encodée en hexadécimal. Quatre erreurs courantes, par ordre de fréquence :
- Signer le JSON re-sérialisé au lieu des octets bruts. La re-sérialisation change les octets et la signature ne correspondra jamais.
- Ignorer l'horodatage. Sans la vérification ±300 s, une livraison capturée peut vous être rejouée indéfiniment.
- Comparer avec == au lieu d'une comparaison à temps constant.
- Ne pas dédupliquer sur X-SmsEmpire-Delivery. Nous garantissons au moins une fois, pas exactement une fois.
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))),
);
}La rotation du secret conserve le précédent valide 24 heures, et les deux signent chaque livraison pendant cette fenêtre — vous pouvez donc déployer sans interruption.
Erreurs
| code | Que faire |
|---|---|
| INVALID_API_KEY | Clé absente, mal formée ou inconnue. Ne réessayez pas. |
| KEY_REVOKED / KEY_EXPIRED | Créez une nouvelle clé. Ne réessayez pas. |
| INSUFFICIENT_SCOPE | Cette clé a été créée sans accès en écriture. |
| API_ACCESS_DENIED | L'API est désactivée pour ce compte, ou vous n'avez pas encore rechargé. |
| API_DISABLED | L'API est temporairement désactivée pour tous. Réessayez plus tard. |
| IDEMPOTENCY_KEY_REQUIRED | Envoyez un en-tête Idempotency-Key à chaque achat. |
| IDEMPOTENCY_KEY_REUSED | Même clé, corps différent. Corrigez votre client. |
| IDEMPOTENCY_IN_PROGRESS | La première tentative est encore en cours. Réessayez dans une seconde. |
| PRICE_ABOVE_MAX | Le prix a dépassé votre max_price. Redemandez un devis et décidez. |
| INSUFFICIENT_BALANCE | Rechargez. Ne réessayez pas avant. |
| COUNTRY_OUT_OF_STOCK | Aucun numéro pour l'instant. Réessayez ou choisissez un autre pays. |
| PROVIDER_CAPACITY | Impossible de fournir ce pays temporairement. Réessayez plus tard. |
| CONCURRENCY_LIMIT | Trop d'activations en attente. Terminez-en ou annulez-en. |
| SPEND_LIMIT | Plafond de dépense horaire atteint. Réessayez après la fenêtre. |
| RATE_LIMITED | Ralentissez. Respectez Retry-After. |
| VALIDATION_ERROR | Le corps de votre requête est incorrect. Ne réessayez pas sans le modifier. |
Cycle de vie d'une activation
Une activation est pending jusqu'à l'arrivée d'un code (received), jusqu'à ce que vous la marquiez utilisée (finished) ou qu'elle soit annulée. L'annulation rembourse exactement ce qui a été facturé.
Une activation en attente expire 20 minutes après l'achat. Nous l'annulons auprès du réseau et vous remboursons intégralement une fois cette annulation confirmée — vous ne payez jamais pour un numéro qui n'a rien reçu.
Un délai de grâce d'environ 120 secondes s'applique avant de pouvoir annuler un numéro ; annuler plus tôt renvoie EARLY_CANCEL_DENIED avec le nombre de secondes à attendre.
Limites
Les requêtes en lecture, en écriture et le total par utilisateur ont chacun un plafond par minute, et des plafonds facultatifs existent sur les activations en attente, la dépense horaire et le nombre d'activations par heure. Vos valeurs actuelles sont dans GET /api/v1/me, où null signifie aucune limite. Pour les augmenter, ouvrez un ticket : ce sont des réglages par compte, pas des valeurs codées en dur.
Référence complète des endpoints
Document OpenAPI 3.1 interactif, toujours généré à partir du code en cours d'exécution.
Ouvrir la référence