Start here
Todo para comenzar antes de la primera solicitud.
EnvoiSMS.ma es una infraestructura de mensajería programable diseñada para Marruecos. Nuestras rutas locales hacia IAM, Inwi y Orange, con conmutación automática entre rutas, mantienen una latencia baja.
Pruebe endpoints en vivo, explore esquemas OpenAPI 3.1 y genere código en cURL, PHP, Node.js, Python y Go al instante.
Genere su clave API "smr_..." desde su Consola EnvoiSMS.ma.
Use la autenticación Bearer en sus cabeceras HTTP para cada petición.
Pruebe su integración con claves Sandbox (env_test_...) en la URL live para validar sus llamadas sin consumir crédito real.
Integre WhatsApp Business API para dividir sus costes de OTP por 10.
Configure un Webhook firmado para recibir los acuses de entrega en tiempo real.
Siga su consumo y sus facturas en MAD directamente en su Dashboard.
// config/services.php
'envoisms' => [
'key' => env('ENVOISMS_API_KEY'),
],
// Usage
Http::withToken(config('services.envoisms.key'))
->post('https://api.envoisms.ma/v1/messages', [
'to' => '+212612345678',
'message' => 'Votre commande est en cours de livraison 🚚',
'from' => 'MaBoutique'
]); OpenAPI Spec Autenticación
Claves Bearer, headers y restricciones de seguridad.
Cada endpoint /v1 requiere una clave API válida transmitida en el header Authorization como token Bearer. Las claves de prueba y producción se pueden generar o revocar desde su consola.
Authorization: Bearer smr_xxx
X-RateLimit-Limit y X-RateLimit-Remaining se devuelven en cada llamada.
Las peticiones de direcciones IP no configuradas devuelven un estado 401.
JSON, Authorization, X-EnvoiSMS.ma-Signature y X-EnvoiSMS.ma-Version están soportados.
POST /v1/messages HTTP/1.1
Host: api.envoisms.ma
Authorization: Bearer smr_xxxxxxxx
Content-Type: application/json
Accept: application/jsonOpen Source
SDKs Oficiales y Librerías
Integre la API de EnvoiSMS en su aplicación con nuestras librerías de código abierto oficiales.
npm install envoismscomposer require envoisms/envoisms-phppip install envoismscomposer require envoisms/laravel-otpgo get github.com/envoisms/envoisms-gonpx -y @envoisms/mcp-serverEnviar un mensaje
Envía un SMS o un mensaje de WhatsApp Business a un destinatario único.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| to | string | Requerido | Número de teléfono en formato E.164 (+212...). |
| message | string | Requerido | Contenido de texto (máx. 1600 caracteres). También se acepta con el nombre "body". |
| from | string | Opcional | Sender ID personalizado (ej: NOMBRE_MARCA). Por defecto "EnvoiSMS". |
| channel | string | Opcional | "sms" o "whatsapp". Por defecto "sms". "whatsapp" envía desde su propio número de WhatsApp Business conectado (si no, 403 WHATSAPP_NOT_CONNECTED). Para enviar un texto ordinario a un cliente que usa WhatsApp basta "sms": sin conexión, sin plantilla, sin ventana de 24 h. Salvo que sea una plantilla, un mensaje de WhatsApp solo se acepta si el contacto le escribió en las últimas 24 horas — si no, 400 OUT_OF_24H_WINDOW, sin ningún cargo (con cascade, se omite WhatsApp y se usa el canal siguiente). |
| cascade | boolean | Opcional | Si está activado, intenta WhatsApp y pasa a SMS: de inmediato si WhatsApp rechaza el mensaje o informa de su fallo (no registrado en WhatsApp, ventana de 24 h cerrada…), o si no llega ningún acuse de entrega dentro de cascade_timeout (120 s por defecto). Solo se factura el envío que realmente salió: un WhatsApp rechazado o fallido no se factura y usted paga solo el SMS. Un WhatsApp que sigue sin acuse de entrega sí salió (aún puede entregarse cuando el teléfono se reconecte): se factura junto con su SMS de respaldo y se reembolsa si WhatsApp informa después de su fallo. Consejo: dé a su plantilla de WhatsApp un tiempo de vida (TTL) no mayor que cascade_timeout, para que un teléfono que vuelve a conectarse no reciba ambos. |
| cascade_timeout | integer | Opcional | Con cascade: segundos de espera de un acuse de entrega antes del siguiente canal, de 30 a 43200 (12 h). Por defecto: 120. Más corto = respaldo más rápido pero más envíos dobles facturados; más largo = menos duplicados. |
| metadata | object | Opcional | Pares clave-valor personalizados almacenados con el mensaje y transmitidos en los webhooks (no se devuelven en los endpoints GET /v1/messages). Una clave tiene significado para la plataforma: purpose: "otp" señala un código de un solo uso que usted genera y activa la reentrega automática (ver notas). |
| buttons | array | Opcional | Array de objetos de botones interactivos de WhatsApp (máx. 3, tipo "copy" | "url" | "call"). |
| interactive | object | Opcional | Objeto de mensaje interactivo enriquecido de WhatsApp: menús de lista desplegable ("list"), botones de respuesta rápida ("button"), formularios nativos ("flow"), botón de enlace ("cta_url": action {"name":"cta_url","parameters":{"display_text","url"}}) o solicitud de ubicación ("location_request_message"). |
| template | object | Opcional | (WhatsApp) Plantilla aprobada de su cuenta: {"name", "language"}, valores en metadata.variables. Único tipo de mensaje permitido fuera de la ventana de 24 horas; tarificado según la categoría de la plantilla. |
| image | video | audio | document | sticker | object | Opcional | (WhatsApp) Multimedia por "id" (medio ya subido) O por "link" (URL https), nunca ambos. "caption" en imagen, vídeo y documento; "filename" en documento. |
| reaction | object | Opcional | (WhatsApp) {"message_id": wamid, "emoji": "👍"} — reacciona a un mensaje de la conversación; un emoji vacío retira la reacción. No se factura. |
| contacts | array | Opcional | (WhatsApp) Fichas de contacto (máx. 20): name.formatted_name obligatorio; phones, emails, urls, addresses, org, birthday opcionales. |
| location | object | Opcional | (WhatsApp) Ubicación: {"latitude", "longitude", "name"?, "address"?}. |
| context | object | Opcional | (WhatsApp) {"message_id": wamid} — responde citando un mensaje anterior. Válido en todos los tipos salvo reaction. |
| phone_number_id | string | Opcional | (WhatsApp) Número conectado que envía; por defecto su número predeterminado. |
- ¿Envía códigos OTP? Dos enfoques. /v1/verify/send gestiona todo el ciclo (generación, entrega, validación, expiración) y sigue siendo la vía recomendada. Si genera sus propios códigos y los envía aquí, añada metadata: {"purpose": "otp"}: ante un fallo de entrega confirmado por la red en un número marroquí mientras el código sigue vigente (menos de 10 minutos), la plataforma lo reenvía automáticamente una vez por una ruta SMS alternativa — mismo identificador de mensaje, sin coste adicional. Sin esta etiqueta, el mensaje se trata como un SMS ordinario.
- WhatsApp: salvo que sea una plantilla, un mensaje (texto, multimedia, reacción, contactos, ubicación, interactivo) solo se envía a un contacto que escribió a su número en las últimas 24 horas; si no, la petición se rechaza con 400 OUT_OF_24H_WINDOW antes de cualquier cargo. Con una clave sandbox el envío sigue simulándose y la respuesta indica el rechazo en "warnings". Para mostrar «escribiendo…» mientras prepara la respuesta, vea POST /v1/messages/typing.
- Los saltos de línea (\n) son totalmente compatibles en todos los canales y se muestran correctamente en el destinatario.
- El formato de texto enriquecido (negrita, cursiva, etc.) no está soportado en el canal SMS (solo texto plano).
- El canal WhatsApp admite el formato de texto con la sintaxis estándar (*negrita*, _cursiva_, ~tachado~).
curl -X POST "https://api.envoisms.ma/v1/messages" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{
"to": "+212612345678",
"message": "Votre code de validation est 849204",
"from": "MaBoutique",
"channel": "whatsapp"
}'{
"id": "msg_8f2d...",
"to": "+212612345678",
"channel": "whatsapp",
"cascade": false,
"status": "queued",
"cost": {
"eur": 0.03,
"mad": 0.33
},
"segments": 1,
"created_at": "2026-05-15T10:30:00Z"
}Envío masivo (Bulk)
Envía hasta 10 000 mensajes en una sola llamada API con destinatarios o contenidos únicos.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| messages | array | Requerido | Array de objetos que contienen "to", "message" (o "body") y un objeto opcional "metadata". |
| from | string | Opcional | Sender ID global para todo el lote. |
| channel | string | Opcional | Canal global ("sms" o "whatsapp"). Por defecto "sms". |
- Los saltos de línea (\n) son totalmente compatibles en los cuerpos de los mensajes masivos.
- El formato de texto enriquecido (negrita, cursiva, etc.) no está soportado en el canal SMS (solo texto plano).
- El canal WhatsApp admite el formato de texto con la sintaxis estándar (*negrita*, _cursiva_, ~tachado~).
curl -X POST "https://api.envoisms.ma/v1/messages/bulk" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"to": "+212611111111",
"message": "Hello Client 1"
},
{
"to": "+212622222222",
"message": "Hello Client 2"
}
],
"from": "ENVOISMS",
"channel": "sms"
}'{
"batch_id": "batch_9a3c...",
"total": 2,
"channel": "sms",
"estimated_cost": {
"eur": 0.056,
"mad": 0.62
},
"messages": [
{
"id": "msg_1a2b...",
"to": "+212611111111",
"status": "queued"
},
{
"id": "msg_3c4d...",
"to": "+212622222222",
"status": "queued"
}
]
}Indicador de escritura de WhatsApp
Marca como leído un mensaje de WhatsApp recibido y muestra «escribiendo…» a su autor mientras prepara la respuesta (desaparece al responder o tras unos 25 s). No es un mensaje: no se almacena ni se factura nada.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| message_id | string | Requerido | Identificador de WhatsApp (wamid) del mensaje recibido al que responde. |
| typing_indicator | boolean | Opcional | false envía solo la confirmación de lectura. Por defecto: true. |
| phone_number_id | string | Opcional | Número conectado que recibió el mensaje; por defecto su número predeterminado. |
curl -X POST "https://api.envoisms.ma/v1/messages/typing" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{}'{
"status": "ok",
"message_id": "wamid.HBgLMjEyNjEyMzQ1Njc4FQIAEhgg",
"typing_indicator": true
}Listar mensajes
Recupera una lista paginada de todos los mensajes enviados desde la cuenta.
| Parámetro | Tipo | Estado | Descripción |
|---|---|---|---|
| limit | integer | Opcional | Número de resultados a devolver (1-200, por defecto: 50). |
| offset | integer | Opcional | Número de resultados a omitir para la paginación (por defecto: 0). |
| status | string | Opcional | Filtrar por estado (queued, sent, delivered, failed, undeliverable, unconfirmed). |
| channel | string | Opcional | Filtrar por canal (sms, whatsapp). |
| from_date | string | Opcional | Filtrar por fecha de inicio (formato ISO 8601). |
| to_date | string | Opcional | Filtrar por fecha de fin (formato ISO 8601). |
curl -X GET "https://api.envoisms.ma/v1/messages?limit=50&offset=0" \
-H "Authorization: Bearer smr_xxx"{
"data": [
{
"id": "msg_8f2d...",
"to": "+212612345678",
"channel": "whatsapp",
"body": "Votre code de validation est 849204",
"sender_id": "MaBanque",
"status": "delivered",
"cost_mad": 0.13,
"created_at": "2026-05-15T10:30:00Z"
}
],
"limit": 50,
"offset": 0,
"total": 1
}Estado del mensaje
Consulta los detalles y el estado de entrega en tiempo real de un mensaje específico.
- Estados posibles: queued, sent, delivered, failed, undeliverable, unconfirmed. "unconfirmed" significa que el operador nunca confirmó ni negó la entrega — un acuse que llegue más tarde todavía puede sustituirlo.
- El campo metadata proporcionado en el envío no se devuelve aquí; se transmite en los webhooks.
curl -X GET "https://api.envoisms.ma/v1/messages/:id" \
-H "Authorization: Bearer smr_xxx"{
"id": "msg_8f2d...",
"campaign_id": null,
"to": "+212612345678",
"channel": "sms",
"body": "Votre code de validation est 849204",
"sender_id": "MaBanque",
"unicode": 0,
"segments": 1,
"status": "delivered",
"error_code": null,
"error_message": null,
"cost_eur": 0.0436,
"cost_mad": 0.48,
"scheduled_at": null,
"sent_at": "2026-05-15T10:30:02Z",
"delivered_at": "2026-05-15T10:30:05Z",
"failed_at": null,
"operator": "Maroc Telecom",
"sandbox": 0,
"created_at": "2026-05-15T10:30:00Z"
}Listar plantillas
Recupera todas las plantillas de WhatsApp y SMS aprobadas de su cuenta.
curl -X GET "https://api.envoisms.ma/v1/templates" \
-H "Authorization: Bearer smr_xxx"{
"data": [
{
"id": "tpl_1234...",
"name": "otp_verification",
"channel": "whatsapp",
"body": "Votre code de validation est {{1}}",
"status": "approved",
"category": "otp",
"created_at": "2026-05-15T10:30:00Z"
}
],
"total": 1
}Crear una plantilla
Envía una nueva plantilla para su aprobación por los operadores o WhatsApp.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| name | string | Requerido | Nombre interno de la plantilla. |
| channel | string | Requerido | "sms" o "whatsapp". |
| body | string | Requerido | Contenido del mensaje con variables (ej: {{1}}). |
| category | string | Opcional | Categoría de la plantilla (ej: "otp", "marketing"). |
| language | string | Opcional | WhatsApp: código de idioma de Meta (ej: "fr", "ar", "en_US"). Por defecto "fr". |
| sample_values | object | string[] | Opcional | WhatsApp: un valor de ejemplo por variable, obligatorio para la revisión de Meta (ej: {"1": "Amine"} o ["Amine"]). |
| header_example_url | string | Opcional | WhatsApp, encabezado de imagen/vídeo/documento: enlace https público a un archivo de ejemplo, enviado a Meta para la revisión. |
| add_security_recommendation | boolean | Opcional | WhatsApp, categoría "authentication": añade el aviso de seguridad de Meta. El texto del cuerpo lo fija Meta; también se acepta code_expiration_minutes (1-90). |
- Una plantilla de WhatsApp pasa primero la revisión de EnvoiSMS (pending_admin) y luego se envía a Meta en su propia cuenta de WhatsApp Business (pending_meta). Solo Meta la aprueba (approved); su categoría final es la que asigna Meta.
curl -X POST "https://api.envoisms.ma/v1/templates" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "tpl_1234...",
"name": "order_confirmation",
"channel": "whatsapp",
"language": "fr",
"status": "pending_admin"
}Sincronizar plantillas desde Meta
Importa todas las plantillas de su cuenta de WhatsApp Business (estado, categoría, calidad). Una plantilla que Meta ya no tiene pasa a deleted_on_meta.
curl -X POST "https://api.envoisms.ma/v1/templates/sync" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{}'{
"ok": true,
"wabas": ["1234567890"],
"fetched": 12,
"updated": 9,
"inserted": 3,
"marked_deleted": 1,
"complete": true,
"errors": []
}Listar Sender IDs
Recupera la lista de sus Sender IDs con su estado de aprobación.
curl -X GET "https://api.envoisms.ma/v1/sender-ids" \
-H "Authorization: Bearer smr_xxx"{
"data": [
{
"id": "sid_9a8b...",
"sender_id": "MABANQUE",
"status": "approved",
"requested_at": "2026-05-10T09:00:00Z"
}
]
}Solicitar un Sender ID
Envía un nuevo Sender ID para su aprobación (requerido para Marruecos).
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| sender_id | string | Requerido | El nombre de remitente deseado (máx. 11 caracteres). |
| rc_url | string | Opcional | Enlace al Registro Mercantil (Registre de Commerce) para verificación. |
curl -X POST "https://api.envoisms.ma/v1/sender-ids" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "sid_9a8b...",
"sender_id": "MABANQUE",
"status": "pending"
}Perfil de WhatsApp Business
Consulta la información pública de su perfil verificado de WhatsApp Business (nombre, foto, descripción, dirección, sitio web).
curl -X GET "https://api.envoisms.ma/v1/whatsapp/profile" \
-H "Authorization: Bearer smr_xxx"{
"about": "Service client officiel EnvoiSMS.ma",
"address": "Casablanca, Maroc",
"description": "Infrastructure de messagerie programmable pour les entreprises au Maroc.",
"email": "[email protected]",
"websites": [
"https://votremarque.ma"
],
"profile_picture_url": "https://pps.whatsapp.net/v/..."
}Actualizar perfil de WhatsApp
Actualiza la información visible para sus clientes en su perfil oficial de WhatsApp Business.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| about | string | Opcional | Estado corto de texto (máx. 139 caracteres). |
| description | string | Opcional | Descripción detallada de su empresa (máx. 512 caracteres). |
| address | string | Opcional | Dirección física de su oficina o tienda. |
| string | Opcional | Dirección de correo electrónico de contacto con el cliente. | |
| websites | array | Opcional | Array con hasta 2 URLs de su sitio web. |
curl -X POST "https://api.envoisms.ma/v1/whatsapp/profile" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{}'{
"success": true,
"updated_at": "2026-08-30T10:00:00Z"
}Generar un OTP
Envía un código de verificación de un solo uso. Dos modos a elegir: "whatsapp" — verificación gestionada, la más simple (EnvoiSMS genera el código y lo entrega por WhatsApp desde un remitente verificado; si Meta rechaza el envío por WhatsApp, el código sale por SMS; con una aplicación Verify configurada con auto_cascade, el SMS también sale al expirar el plazo del canal, 30 s por defecto; ningún código que almacenar de su lado) — o "sms" — usted mantiene el control total del código, la plantilla y el remitente. En ambos casos, la validación se hace con la misma llamada /v1/verify/check. ¿Quiere su propia marca en el respaldo SMS de la verificación gestionada? Disponible bajo petición: [email protected].
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| to | string | Requerido | Destinatario en formato E.164. |
| channel | string | Opcional | "whatsapp" (verificación gestionada: WhatsApp, respaldo SMS si Meta rechaza el envío o, con auto_cascade, tras el plazo del canal; código válido 10 min, 3 intentos, facturada por verificación a la tarifa WhatsApp OTP) o "sms" (código generado para usted, enviado con su marca). Por defecto: "sms". |
| app_id | string | Opcional | ID de la aplicación Verify configurada en el panel de control (ej: vra_...). Aplica automáticamente los parámetros de código, tiempos y la cascada de canales. |
| brand | string | Opcional | (Canal sms) Nombre de marca mostrado (ej: MonApp, máx. 32 car.). Por defecto "EnvoiSMS". |
| code_length | integer | Opcional | (Canal sms) Longitud del código generado (de 4 a 8 dígitos, por defecto: 6). |
| expiry | integer | Opcional | Duración de validez del código en segundos (de 60 a 1800, por defecto: 600). En el canal whatsapp se limita a 600 (límite de la plantilla de autenticación). |
| cascade | array | Opcional | (Canal sms) Lista ordenada de canales para el cambio automático en cascada (ej: ["whatsapp", "sms"]). |
| template | string | Opcional | (Canal sms) Texto personalizado con las variables {{code}} y {{brand}}. En modo whatsapp, el mensaje localizado (fr/en/es) se gestiona por usted. |
| otp_button_text | string | Opcional | (Canal whatsapp) Etiqueta personalizada para el botón de copia automática de WhatsApp (máx. 25 car.). |
| web_otp_domain | string | Opcional | (Opcional, canal sms) Dominio web de destino para autocompletado W3C WebOTP (ej: "https://misitio.ma"). Agrega @dominio #code al SMS. |
| app_hash | string | Opcional | (Opcional, canal sms) Hash de firma de la app Android de 11 caracteres (SMS Retriever API) para detección automática en Android. |
- Autocompletado OTP (iOS y Android): Para permitir que sus usuarios completen automáticamente el código recibido con 1 toque sobre su teclado, simplemente agregue autocomplete="one-time-code" e inputmode="numeric" al campo <input> de su sitio.
- Tiempo de espera anti-spam (cooldown): El campo "resend_after_seconds" indica el tiempo de espera exacto antes de reintentar (60 s para WhatsApp según Meta, 30 s para SMS). Se aplica un límite diario de 10 verificaciones por número.
curl -X POST "https://api.envoisms.ma/v1/verify/send" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{
"to": "+212612345678",
"brand": "MonApp",
"channel": "whatsapp",
"code_length": 6,
"expiry": 600
}'{
"session_id": "vrf_7e2a...",
"to": "+212612345678",
"channel": "whatsapp",
"resend_after_seconds": 60,
"fallback_channels": ["sms", "whatsapp"],
"expires_at": "2026-05-15T10:35:00Z",
"status": "sent",
"cost": { "eur": 0.05, "mad": 0.55 }
}Reenviar código OTP (SMS o WhatsApp)
Reenvía el mismo código OTP activo por SMS o WhatsApp tras expirar el tiempo de espera (30 s para SMS, 60 s para WhatsApp). No genera nuevo código, evitando conflictos.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| session_id | string | Requerido | Identificador de sesión recibido en la llamada inicial a /v1/verify/send. |
| channel | string | Opcional | Canal de reenvío objetivo ("sms" | "whatsapp"). Por defecto: "sms". |
- Disponible en todos los planes, incluido Starter: /v1/verify/resend requiere el mismo permiso "verify" que /v1/verify/send, nada más.
- Desde dónde cuenta: el tiempo de espera corre desde la última entrega real, no desde su llamada — 60 s tras una entrega WhatsApp, 30 s tras un SMS. GET /v1/verify/{session_id} devuelve la cuenta atrás en curso ("resend_available_in_seconds") y la cabecera Retry-After del 429 lleva los segundos exactos restantes. La ventana es propia de su cuenta y del número: el tráfico de otro cliente hacia el mismo número nunca le bloquea.
- Claves sandbox (env_test_): no se entrega nada y no se cobra nada. La respuesta lleva entonces "sandbox": true y "sandbox_code", para que un reenvío simulado nunca se confunda con uno realmente entregado. Una clave sandbox solo puede reenviar sesiones que ella creó, y una clave live solo sesiones live.
curl -X POST "https://api.envoisms.ma/v1/verify/resend" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{}'{
"session_id": "vrf_7e2a...",
"message_id": "wamid.HBgMMjEy...",
"to": "+212612345678",
"channel": "whatsapp",
"status": "sent",
"resend_after_seconds": 60,
"expires_at": "2026-05-15T10:35:00Z"
}Verificar un OTP
Valida el código proporcionado por el usuario para una sesión de verificación determinada.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| session_id | string | Requerido | ID de sesión recibido en la llamada a /v1/verify/send. |
| code | string | Requerido | El código recibido e introducido por el usuario. |
curl -X POST "https://api.envoisms.ma/v1/verify/check" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{
"session_id": "vrf_7e2a...",
"code": "849204"
}'{
"session_id": "vrf_7e2a...",
"verified": true,
"verified_at": "2026-05-15T10:35:12Z"
}Estado de la sesión OTP
Consulta el estado (validado o expirado) de una sesión de verificación específica.
curl -X GET "https://api.envoisms.ma/v1/verify/:id" \
-H "Authorization: Bearer smr_xxx"{
"id": "vrf_7e2a...",
"to": "+212612345678",
"channel": "whatsapp",
"expires_at": "2026-05-15T10:40:00Z",
"verified_at": "2026-05-15T10:35:12Z",
"created_at": "2026-05-15T10:30:00Z"
}Validación de número
Valida el formato, el operador (carrier), el tipo de línea y la ubicación geográfica de un número de teléfono.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| number | string | Requerido | El número de teléfono a validar (formato local o internacional). |
| country_code | string | Opcional | Código de país ISO de 2 letras (ej: MA, FR). Recomendado si el número está en formato local. |
curl -X POST "https://api.envoisms.ma/v1/verify/lookup" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{
"number": "+212612345678",
"country_code": "MA"
}'{
"valid": true,
"number": "212612345678",
"local_format": "0612345678",
"international_format": "+212612345678",
"country_prefix": "212",
"country_code": "MA",
"country_name": "Morocco",
"location": "Casablanca",
"carrier": "Maroc Telecom (IAM)",
"line_type": "mobile"
}Listar conversaciones
Recupera el listado de conversaciones de WhatsApp con seguimiento en tiempo real de la ventana de servicio de 24 horas y estado del bot.
| Parámetro | Tipo | Estado | Descripción |
|---|---|---|---|
| limit | integer | Opcional | Número máximo de conversaciones a devolver (por defecto 30, máx. 100). |
| bot_status | string | Opcional | Filtrar por estado del bot ("active" o "muted"). |
curl -X GET "https://api.envoisms.ma/v1/conversations?limit=50&offset=0" \
-H "Authorization: Bearer smr_xxx"{
"conversations": [
{
"phone": "+212612345678",
"contact_name": "Yassine Alami",
"last_message": "Bonjour, je souhaite visiter l'appartement témoin",
"last_message_at": "2026-08-30T11:42:00Z",
"unread_count": 1,
"bot_muted": true,
"can_reply_free": true,
"window_expires_at": "2026-08-31T11:42:00Z"
}
],
"total": 1
}Historial de conversación
Recupera el hilo completo de mensajes intercambiados con un contacto de WhatsApp.
curl -X GET "https://api.envoisms.ma/v1/conversations/:phone" \
-H "Authorization: Bearer smr_xxx"{
"phone": "+212612345678",
"contact_name": "Yassine Alami",
"bot_muted": true,
"messages": [
{
"id": "msg_01J...",
"direction": "inbound",
"body": "Je souhaite des informations sur le projet Casablanca Marina",
"type": "text",
"timestamp": "2026-08-30T11:40:00Z"
},
{
"id": "msg_02J...",
"direction": "outbound",
"body": "Bonjour Yassine ! Quel type de bien recherchez-vous ?",
"type": "interactive",
"timestamp": "2026-08-30T11:40:05Z"
}
]
}Responder en chat en vivo
Envía una respuesta de agente y silencia el bot en esa conversación (reactivación con POST /v1/conversations/:phone/toggle-bot). Un mensaje libre de WhatsApp exige que el contacto haya escrito en las últimas 24 horas (si no, 400 OUT_OF_24H_WINDOW); una plantilla aprobada puede enviarse en cualquier momento.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| message | string | Opcional | Texto de la respuesta (o pie del medio adjunto). Obligatorio sin plantilla ni medio. |
| channel | string | Opcional | "whatsapp" (por defecto) o "sms". |
| template_name | string | Opcional | Plantilla de WhatsApp aprobada que se envía en lugar de texto libre (con template_language y variables). |
| media_url | string | Opcional | URL https (o data URI) de una imagen o PDF que adjuntar; media_type "image" o "document". |
| phone_number_id | string | Opcional | Número de WhatsApp conectado que responde. |
curl -X POST "https://api.envoisms.ma/v1/conversations/:phone/messages" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{}'{
"ok": true,
"message": {
"id": "msg_03J...",
"phone": "212612345678",
"role": "agent",
"message": "Bonjour Yassine, votre visite est confirmée.",
"status": "sent",
"channel": "whatsapp",
"created_at": "2026-08-30T11:45:00Z"
}
}Listar leads calificados
Recupera los clientes potenciales calificados automáticamente por AtlasAI™ con su puntuación BANT y estado CRM.
| Parámetro | Tipo | Estado | Descripción |
|---|---|---|---|
| stage | string | Opcional | Filtrar por etapa ("new", "contacted", "meeting_scheduled", "closed_won", "closed_lost"). |
| min_score | integer | Opcional | Puntuación BANT mínima (0 a 100). |
- Los leads son calificados por AtlasAI™ Conversational Engine, AtlasAI™ Voice y AtlasAI™ Vision directamente a través de mensajes de WhatsApp. Estos motores de IA están integrados de forma nativa en WABA y no se ofrecen como API independientes.
curl -X GET "https://api.envoisms.ma/v1/qualified-leads?limit=50&offset=0" \
-H "Authorization: Bearer smr_xxx"{
"leads": [
{
"id": "lead_01J...",
"phone": "+212612345678",
"name": "Dr. Benjelloun",
"preset": "medical_equipment",
"bant_score": 85,
"is_hot": true,
"breakdown": {
"budget": 25,
"authority": 25,
"need": 20,
"timeline": 15
},
"intent": "Échographe Doppler pour nouveau cabinet",
"stage": "meeting_scheduled",
"created_at": "2026-08-30T09:15:00Z"
}
],
"total": 1
}Actualizar estado del lead
Actualiza la etapa en el pipeline comercial de un lead calificado.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| status | string | Requerido | "new" | "contacted" | "meeting_scheduled" | "closed_won" | "closed_lost" |
| notes | string | Opcional | Notas internas de seguimiento comercial. |
curl -X POST "https://api.envoisms.ma/v1/qualified-leads/:id/status" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{}'{
"success": true,
"lead_id": "lead_01J...",
"stage": "meeting_scheduled"
}Listar contactos
Recupera todos los contactos de su cuenta, con filtrado por palabra clave o por lista.
| Parámetro | Tipo | Estado | Descripción |
|---|---|---|---|
| limit | integer | Opcional | Resultados por página (1-500, por defecto: 100). |
| offset | integer | Opcional | Desplazamiento de paginación (por defecto: 0). |
| q | string | Opcional | Búsqueda por nombre, teléfono o email. |
| list_id | string | Opcional | Filtrar únicamente los miembros de una lista de contactos específica. |
curl -X GET "https://api.envoisms.ma/v1/contacts?limit=50&offset=0" \
-H "Authorization: Bearer smr_xxx"{
"data": [
{
"id": "ctc_a2b3...",
"phone": "+212612345678",
"name": "Karim Bennani",
"email": "[email protected]",
"custom1": "VIP",
"custom2": null,
"custom3": null,
"created_at": "2026-05-10T14:20:00Z"
}
],
"limit": 100,
"offset": 0,
"total": 1
}Crear / Modificar un contacto
Añade un contacto o actualiza la información de un contacto existente (detección por número).
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| phone | string | Requerido | Número de teléfono en formato E.164. |
| name | string | Opcional | Nombre completo del contacto. |
| string | Opcional | Dirección de email. | |
| list_id | string | Opcional | Asociar inmediatamente el contacto a una lista existente. |
| custom_fields | object | Opcional | Objeto que contiene hasta 3 campos personalizados ("custom1", "custom2", "custom3"). |
curl -X POST "https://api.envoisms.ma/v1/contacts" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{
"phone": "+212612345678",
"name": "Karim Bennani",
"email": "[email protected]",
"list_id": "lst_f84b..."
}'{
"id": "ctc_a2b3...",
"phone": "+212612345678",
"name": "Karim Bennani",
"email": "[email protected]",
"custom1": "VIP",
"custom2": null,
"custom3": null,
"created_at": "2026-05-10T14:20:00Z"
}Importar contactos
Importa masivamente hasta 5 000 contactos en una sola llamada.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| contacts | array | Requerido | Array de objetos que contienen "phone", "name" (opcional) y "email" (opcional). |
| list_id | string | Opcional | ID de la lista en la que importar el grupo. |
curl -X POST "https://api.envoisms.ma/v1/contacts/import" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{
"phone": "+212611111111",
"name": "Karim"
},
{
"phone": "+212622222222",
"name": "Youssef"
}
],
"list_id": "lst_f84b..."
}'{
"imported": 150,
"skipped": 3
}Eliminar un contacto
Elimina definitivamente un contacto a partir de su identificador único.
curl -X DELETE "https://api.envoisms.ma/v1/contacts/:id" \
-H "Authorization: Bearer smr_xxx"{
"deleted": true,
"id": "ctc_a2b3..."
}Listar las listas
Recupera todas las listas de contactos creadas para las campañas de difusión.
curl -X GET "https://api.envoisms.ma/v1/contacts/lists" \
-H "Authorization: Bearer smr_xxx"{
"data": [
{
"id": "lst_f84b...",
"name": "Newsletter Clients",
"description": "Clients inscrits à notre lettre d'information",
"count": 1420,
"created_at": "2026-04-15T09:00:00Z"
}
]
}Crear una lista
Crea un nuevo grupo (lista de contactos) vacío destinado a las campañas.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| name | string | Requerido | Nombre de la lista. |
| description | string | Opcional | Descripción de la lista. |
curl -X POST "https://api.envoisms.ma/v1/contacts/lists" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Newsletter Clients",
"description": "Clients inscrits"
}'{
"id": "lst_f84b...",
"name": "Newsletter Clients",
"description": "Clients inscrits",
"count": 0
}Listar las bajas
Recupera la lista de números que se han dado de baja (STOP) de sus comunicaciones.
curl -X GET "https://api.envoisms.ma/v1/optouts" \
-H "Authorization: Bearer smr_xxx"{
"data": [
{
"phone": "+212611111111",
"opted_out_at": "2026-06-01T12:00:00Z"
}
],
"total": 1
}Añadir una baja
Añade manualmente un número a su lista de bajas (blacklist global).
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| phone | string | Requerido | Número de teléfono en formato E.164. |
curl -X POST "https://api.envoisms.ma/v1/optouts" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{}'{
"success": true,
"phone": "+212611111111",
"opted_out_at": "2026-06-01T12:00:00Z"
}Retirar una baja
Retira un número de la lista de bajas.
curl -X DELETE "https://api.envoisms.ma/v1/optouts/:phone" \
-H "Authorization: Bearer smr_xxx"{
"deleted": true,
"phone": "+212611111111"
}Listar consentimientos
Registro de consentimientos de solo adición (ley 09-08, art. 10): cada consentimiento y cada retirada es un evento fechado con su fuente y su prueba. La plataforma escribe por sí misma las palabras clave STOP/START recibidas en WhatsApp y SMS y la primera conversación abierta por el cliente. Añada format=csv para exportar todo el conjunto filtrado.
| Parámetro | Tipo | Estado | Descripción |
|---|---|---|---|
| phone | string | Opcional | Número E.164 a filtrar. |
| purpose | string | Opcional | marketing, transactional, otp o service. |
| status | string | Opcional | granted o withdrawn. |
| from | string | Opcional | Eventos registrados a partir de esta fecha ISO 8601. |
| format | string | Opcional | csv para descargar el conjunto completo como archivo. |
curl -X GET "https://api.envoisms.ma/v1/consents?limit=50&offset=0" \
-H "Authorization: Bearer smr_xxx"{
"data": [
{
"id": "cns_9f2a...",
"phone": "+212612345678",
"channel": "whatsapp",
"purpose": "marketing",
"status": "withdrawn",
"source": "whatsapp_keyword",
"evidence": { "keyword": "stop", "lang": "fr" },
"recorded_at": "2026-09-05T09:00:00.000Z",
"created_at": "2026-09-05T09:00:00.000Z"
}
],
"total": 1,
"limit": 100,
"offset": 0
}Registrar un consentimiento
Consigna que una persona dio o retiró su consentimiento, con la prueba que usted posee (texto mostrado, URL, IP, referencia). recorded_at puede antedatarse para un consentimiento importado, nunca en el futuro. Una retirada de marketing pasa de inmediato a la lista de exclusión.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| phone | string | Requerido | Número en formato E.164. |
| purpose | string | Requerido | marketing, transactional, otp o service. |
| status | string | Opcional | granted (por defecto) o withdrawn. |
| channel | string | Opcional | whatsapp, sms o any (por defecto). |
| source | string | Opcional | api (por defecto), form, import o dashboard. Las fuentes por palabra clave están reservadas a la plataforma. |
| evidence | object | Opcional | Objeto JSON libre (< 4 KB): texto mostrado, url, ip, referencia. |
| recorded_at | string | Opcional | Fecha ISO 8601 del acto de la persona (por defecto: ahora). |
curl -X POST "https://api.envoisms.ma/v1/consents" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "cns_9f2a...",
"phone": "+212612345678",
"channel": "any",
"purpose": "marketing",
"status": "granted",
"source": "form",
"evidence": { "text": "J'accepte de recevoir les offres par WhatsApp", "url": "https://example.ma/inscription" },
"recorded_at": "2026-09-01T10:00:00.000Z",
"created_at": "2026-09-09T12:00:00.000Z"
}Estado e historial de un número
La respuesta a «muéstreme el consentimiento de esta persona»: estado actual por finalidad (derivado del evento más reciente, unknown sin historial, nunca presumido), presencia en la lista de exclusión e historial completo.
curl -X GET "https://api.envoisms.ma/v1/consents/:phone" \
-H "Authorization: Bearer smr_xxx"{
"phone": "+212612345678",
"opted_out": true,
"opted_out_at": "2026-09-05T09:00:00Z",
"state": {
"marketing": { "status": "withdrawn", "channel": "whatsapp", "source": "whatsapp_keyword", "recorded_at": "2026-09-05T09:00:00.000Z", "record_id": "cns_3" },
"transactional": { "status": "unknown" },
"otp": { "status": "unknown" },
"service": { "status": "granted", "channel": "whatsapp", "source": "whatsapp_inbound", "recorded_at": "2026-07-01T09:00:00.000Z", "record_id": "cns_1" }
},
"history": []
}Listar campañas
Recupera todas sus campañas de envío programado o de difusión en curso.
curl -X GET "https://api.envoisms.ma/v1/campaigns" \
-H "Authorization: Bearer smr_xxx"{
"data": [
{
"id": "cmp_8d2a...",
"name": "Soldes d'été 2026",
"channel": "sms",
"list_id": "lst_f84b...",
"template_id": null,
"body": "Bonjour {{name}}, profitez de -50% sur toute la collection avec le code ETE50 !",
"sender_id": "SOLDES",
"status": "draft",
"scheduled_at": null,
"created_at": "2026-06-01T12:00:00Z"
}
]
}Crear una campaña
Registra una nueva campaña como borrador o la programa para una fecha concreta.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| name | string | Requerido | Nombre de la campaña. |
| body | string | Requerido | Cuerpo del mensaje. Puede usar la variable {{name}}. (Se requiere uno de los dos: body o template_id.) |
| template_id | string | Requerido | Alternativamente, ID de una plantilla aprobada. (Se requiere uno de los dos: body o template_id.) |
| channel | string | Opcional | "sms" o "whatsapp". Por defecto "sms". |
| list_id | string | Opcional | ID de la lista de contactos destinataria. |
| sender_id | string | Opcional | Nombre de remitente. |
| scheduled_at | string | Opcional | Fecha de programación (ISO 8601). Cambia el estado a "scheduled". |
| buttons | array | Opcional | Array de botones interactivos de WhatsApp (máx. 3, tipo "copy" | "url" | "call"). |
| metadata | object | Opcional | Metadatos personalizados (ej: opciones de throttling / cadencia de envío: {"throttling": "50_min"}). |
curl -X POST "https://api.envoisms.ma/v1/campaigns" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Soldes d'été 2026",
"body": "Bonjour {{name}}, profitez de -50% avec le code ETE50 !",
"channel": "sms",
"list_id": "lst_f84b...",
"sender_id": "SOLDES"
}'{
"id": "cmp_8d2a...",
"status": "draft"
}Detalles de una campaña
Recupera los detalles, la programación y el estado de ejecución de una campaña.
curl -X GET "https://api.envoisms.ma/v1/campaigns/:id" \
-H "Authorization: Bearer smr_xxx"{
"id": "cmp_8d2a...",
"name": "Soldes d'été 2026",
"channel": "sms",
"list_id": "lst_f84b...",
"body": "Bonjour {{name}}, profitez de -50%...",
"sender_id": "SOLDES",
"status": "running",
"total_count": 1420,
"sent_count": 840,
"started_at": "2026-06-15T10:00:00Z",
"created_at": "2026-06-01T12:00:00Z"
}Lanzar una campaña
Inicia inmediatamente la difusión de una campaña en borrador a todos los contactos asociados.
curl -X POST "https://api.envoisms.ma/v1/campaigns/:id/send" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "cmp_8d2a...",
"queued": 1420,
"total": 1420
}Listar webhooks
Recupera la lista de todos sus endpoints de webhooks registrados.
curl -X GET "https://api.envoisms.ma/v1/webhooks" \
-H "Authorization: Bearer smr_xxx"{
"data": [
{
"id": "whk_5f2b...",
"url": "https://mon-serveur.ma/api/envoisms-receiver",
"events": [
"message.delivered",
"message.failed"
],
"active": true,
"last_triggered_at": "2026-06-15T09:30:15Z",
"last_status": 200,
"created_at": "2026-05-01T10:00:00Z"
}
]
}Crear un Webhook
Registra una URL HTTPS de callback para recibir las notificaciones de eventos.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| url | string | Requerido | URL de destino segura que comience por "https://". |
| events | array | Opcional | Array de eventos (ej: ["message.delivered", "message.failed"]). Se aceptan los comodines "message.*" y "*". Por defecto: ["message.delivered", "message.failed"]. |
| secret | string | Opcional | Clave de firma secreta. Si no se proporciona, se generará automáticamente. |
curl -X POST "https://api.envoisms.ma/v1/webhooks" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://mon-serveur.ma/api/envoisms-receiver",
"events": [
"message.delivered",
"message.failed"
]
}'{
"id": "whk_5f2b...",
"url": "https://mon-serveur.ma/api/envoisms-receiver",
"events": [
"message.delivered",
"message.failed"
],
"secret": "whsec_2f8a9e7d...",
"active": true
}Modificar un Webhook
Actualiza la configuración de un webhook (URL, eventos suscritos o estado activo).
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| url | string | Opcional | Nueva URL HTTPS. |
| events | array | Opcional | Nueva lista de eventos suscritos. |
| active | boolean | Opcional | Activar (true) o desactivar (false) el webhook. |
curl -X PATCH "https://api.envoisms.ma/v1/webhooks/:id" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{
"url": "https://mon-serveur.ma/api/envoisms-receiver-updated",
"active": false
}'{
"updated": true,
"id": "whk_5f2b..."
}Eliminar un Webhook
Desactiva y elimina lógicamente un endpoint de webhook.
curl -X DELETE "https://api.envoisms.ma/v1/webhooks/:id" \
-H "Authorization: Bearer smr_xxx"{
"deleted": true,
"id": "whk_5f2b..."
}Probar un Webhook
Dispara un evento de prueba ("message.test") hacia la URL configurada del webhook.
curl -X POST "https://api.envoisms.ma/v1/webhooks/:id/test" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{}'{
"queued": true,
"id": "whk_5f2b..."
}Listar Inbound Webhooks
Recupera todos los endpoints de captura de leads publicitarios configurados en su cuenta.
curl -X GET "https://api.envoisms.ma/v1/inbound-webhooks" \
-H "Authorization: Bearer smr_xxx"{
"webhooks": [
{
"id": "inw_9a8b...",
"name": "Campagne TikTok Ads Casablanca",
"source": "tiktok_ads",
"catch_url": "https://api.envoisms.ma/v1/inbound-webhooks/catch/inw_9a8b...",
"auto_start_funnel": true,
"total_received": 142,
"created_at": "2026-08-20T14:00:00Z"
}
]
}Crear un Inbound Webhook
Genera una URL de captura para recibir prospectos de TikTok Lead Ads, Meta Lead Ads, Zapier o Make al instante.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| name | string | Requerido | Nombre descriptivo de la fuente (ej: "Facebook Lead Gen Promo Verano"). |
| source | string | Opcional | Identificador de fuente ("tiktok_ads", "meta_leads", "google_forms", "custom"). Por defecto: "custom". |
| auto_start_funnel | boolean | Opcional | Si es true, activa inmediatamente el flujo de calificación de WhatsApp AtlasAI™ al recibir el lead. |
curl -X POST "https://api.envoisms.ma/v1/inbound-webhooks" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{}'{
"id": "inw_9a8b...",
"name": "Campagne TikTok Ads Casablanca",
"catch_url": "https://api.envoisms.ma/v1/inbound-webhooks/catch/inw_9a8b...",
"auto_start_funnel": true
}Consultar el saldo
Consulta el saldo disponible en dírhams marroquíes (MAD) junto con la divisa y el plan activo.
curl -X GET "https://api.envoisms.ma/v1/billing/balance" \
-H "Authorization: Bearer smr_xxx"{
"balance_mad": 1492.5,
"currency": "MAD",
"plan": "croissance"
}Estado de pagos máquina
Indica si los pagos máquina (MPP / HTTP 402) están activos en esta instancia. Un agente puede consultar este endpoint antes de intentar una llamada de pago.
- Cuando "enabled" es false, cualquier llamada de pago devuelve 503 en lugar de 402.
curl -X GET "https://api.envoisms.ma/v1/machine-payments/status" \
-H "Authorization: Bearer smr_xxx"{
"enabled": true,
"deposit_usd": 5.00,
"credit_mad": 49.65,
"networks": ["base", "tempo"],
"permissions": ["send", "status", "balance", "verify"]
}Desafío de pago (HTTP 402)
Cualquier endpoint de pago llamado sin clave API devuelve un 402 con una cabecera WWW-Authenticate: Payment que contiene un desafío firmado y una dirección de depósito USDC. El agente deposita exactamente deposit_usd USDC en una de las direcciones y reintenta la llamada con una credencial Payment.
- El campo "request" es JSON canónico (RFC 8785) codificado en base64url sin relleno. Contiene: amount, currency, networks[], recipients{}, paymentIntent, resource, credit{}.
- Deduplicación por IP: si ya existe un desafío activo para la misma IP, se devuelve el mismo desafío en lugar de una nueva dirección de depósito.
- Límite: 5 desafíos por minuto por IP. A partir de ahí, se devuelve el 402 sin dirección de depósito.
curl -X POST "https://api.envoisms.ma/v1/messages" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{
"to": "+212612345678",
"message": "Votre code de validation est 849204",
"from": "MaBoutique",
"channel": "whatsapp"
}'HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment id="chal_01jxyz", realm="envoisms",
method="stripe", intent="session",
request="<base64url-JCS-encoded JSON>",
expires="2026-09-25T21:45:00.000Z",
description="EnvoiSMS pay-per-use API access."
{
"type": "https://paymentauth.org/problems/payment-required",
"title": "Payment Required",
"status": 402,
"detail": "Payment is required.",
"challengeId": "chal_01jxyz"
}Canjear una credencial de pago
Tras depositar los fondos USDC, el agente reintenta el endpoint de pago original (ej: POST /v1/messages) sustituyendo Authorization: Bearer por Authorization: Payment <credential>. El worker valida el depósito en Stripe en el lado del servidor, acredita una cuenta máquina y devuelve la clave API con alcance limitado en la cabecera X-Machine-Session-Key.
- La credencial es JSON canónico base64url: {"challenge":{"id":"chal_01jxyz","method":"stripe","intent":"session"},"source":"<wallet-address>","payload":{}}.
- La clave devuelta se muestra una sola vez. Idempotente: un agente que pierde su clave puede volver a llamar — el mismo depósito devuelve una nueva clave para la misma cuenta.
- Devuelve 402 si el depósito en Stripe aún no ha alcanzado el estado "succeeded". El agente debe esperar la confirmación on-chain (normalmente 1–2 minutos) antes de reintentar.
curl -X POST "https://api.envoisms.ma/v1/messages (+ /v1/verify, /v1/waba)" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{}'{
"key": "smr_live_xxxxxxxxxxxxxxxxxxxx",
"permissions": ["send", "status", "balance", "verify"],
"credit_mad": 49.65,
"receipt": "<base64url-encoded receipt>",
"account": {
"id": "acct_01jxyz",
"plan": "starter",
"balance_mad": 49.65
}
}Estadísticas de uso
Recupera métricas clave sobre sus envíos (volúmenes totales, tasa de entregabilidad y costes facturados).
curl -X GET "https://api.envoisms.ma/v1/analytics" \
-H "Authorization: Bearer smr_xxx"{
"summary": {
"total": 12840,
"delivered": 12570,
"delivery_rate": 97.9,
"cost_mad": 2663.1
}
}Listar claves API
Recupera la lista de todas sus claves de API activas o revocadas.
curl -X GET "https://api.envoisms.ma/v1/api-keys" \
-H "Authorization: Bearer smr_xxx"{
"data": [
{
"id": "key_e84c...",
"name": "Production Server",
"key_prefix": "smr_a8f7d6c5",
"sandbox": false,
"ip_whitelist": [
"196.200.1.4"
],
"rate_limit": 100,
"permissions": [
"send",
"verify",
"status"
],
"active": true,
"last_used_at": "2026-06-15T10:30:00Z",
"created_at": "2026-05-01T08:00:00Z"
}
]
}Crear una clave API
Genera un nuevo token de API seguro con permisos y restricciones específicos.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| name | string | Opcional | Etiqueta para identificar la clave (por defecto: "API key"). |
| permissions | array | Opcional | Derechos concedidos (send, verify, status, balance, contacts, campaigns, webhooks, billing, analytics, api_keys, optouts, sender_id). |
| ip_whitelist | array | Opcional | Lista de direcciones IP autorizadas a ejecutar peticiones con esta clave. |
| rate_limit | integer | Opcional | Límite máximo de peticiones/min (de 10 a 2000). Por defecto según el plan: Starter 60, Business 180, Pro 500, Enterprise 1 200. |
| sandbox | boolean | Opcional | true genera una clave de prueba con el prefijo env_test_. Las peticiones se validan y registran, pero ningún mensaje se envía realmente y nada se factura. El modo de una clave es definitivo: para cambiarlo, cree una nueva clave. |
curl -X POST "https://api.envoisms.ma/v1/api-keys" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Server",
"permissions": [
"send",
"verify",
"status"
],
"ip_whitelist": [
"196.200.1.4"
],
"rate_limit": 100
}'{
"id": "key_e84c...",
"name": "Production Server",
"key_prefix": "smr_a8f7d6c5",
"api_key": "smr_a8f7d6c5b4a3...",
"sandbox": false,
"permissions": [
"send",
"verify",
"status"
],
"rate_limit": 100,
"warning": "The full API key is shown once. Store it securely."
}Modificar una clave API
Actualiza los permisos, restricciones de IP o el estado de activación de una clave API.
| Campo | Tipo | Estado | Descripción |
|---|---|---|---|
| name | string | Opcional | Nuevo nombre. |
| permissions | array | Opcional | Nueva lista de permisos. |
| ip_whitelist | array | Opcional | Nueva lista de direcciones IP autorizadas. |
| rate_limit | integer | Opcional | Nuevo límite de peticiones por minuto. |
| active | boolean | Opcional | Activar o suspender la clave. |
curl -X PATCH "https://api.envoisms.ma/v1/api-keys/:id" \
-H "Authorization: Bearer smr_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "Backup Server",
"active": true
}'{
"updated": true,
"id": "key_e84c..."
}Revocar una clave API
Revoca definitivamente una clave API para impedir que autentique peticiones.
curl -X DELETE "https://api.envoisms.ma/v1/api-keys/:id" \
-H "Authorization: Bearer smr_xxx"{
"revoked": true,
"id": "key_e84c..."
}Guía de Webhooks
Callbacks de entrega firmados y seguros.
EnvoiSMS.ma envía eventos JSON a la URL HTTPS de su servidor con un encabezado X-EnvoiSMS.ma-Signature para autenticar al remitente.
message.sentEl mensaje fue aceptado por la red del operador.
message.deliveredEl mensaje fue entregado con éxito al destinatario (SMS o WhatsApp).
message.readEl mensaje de WhatsApp fue abierto y leído por el destinatario (doble check azul).
message.failedFallo de entrega en la ruta o en la presentación (rechazo, error de encaminamiento).
message.undeliverableLa red confirmó que el mensaje no puede entregarse (número inexistente, expirado en la cola del operador).
message.fallbackCascada (cascade: true): el mensaje pasa al siguiente canal (p. ej. WhatsApp → SMS) porque el primero rechazó o informó del fallo del mensaje, o no envió acuse de entrega dentro de cascade_timeout. channel es el nuevo canal, previous_channel el anterior, error_code y error_message el motivo.
message.inboundUn destinatario respondió a uno de sus mensajes SMS o WhatsApp (entrante).
message.flow_responseUn usuario completó y envió un formulario nativo de WhatsApp Flow.
message.locationUn usuario compartió su ubicación geográfica GPS en WhatsApp.
lead.qualifiedAtlasAI™ completó la calificación BANT de un cliente potencial en WhatsApp.
contact.optoutUn destinatario se dio de baja (palabra clave STOP o similar).
import crypto from 'node:crypto';
// La signature arrive dans le header X-EnvoiSMS-Signature
// (format "sha256=<hex>"), l'événement dans X-EnvoiSMS-Event.
// Corps livré : { "event": "...", "data": { ... }, "timestamp": "..." }
export function verifySignature(body: string, sig: string, secret: string) {
const hmac = crypto.createHmac('sha256', secret)
.update(body)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from('sha256=' + hmac));
} OpenAPI Spec Errores
Estructura de las respuestas de error de la API.
Todos los errores de la API devuelven un código HTTP adecuado (4xx o 5xx) junto con una estructura JSON predecible que contiene el código de error y la descripción.
{
"error": {
"code": "INVALID_PHONE",
"message": "to must be E.164 format, for example +212612345678",
"docs": "https://envoisms.ma/docs#errors"
}
}UNAUTHORIZEDClave API ausente o no válida.
INSUFFICIENT_BALANCESaldo insuficiente para realizar el envío.
INVALID_PHONEEl número de teléfono no está en formato E.164.
WHATSAPP_NOT_CONNECTEDNingún número de WhatsApp Business conectado: conecte el suyo en el panel para enviar por WhatsApp. Para un texto ordinario, omita channel (SMS por defecto) — no se ha cobrado nada.
OUT_OF_24H_WINDOWMensaje libre de WhatsApp a un contacto que no le ha escrito en las últimas 24 horas. Envíe una plantilla aprobada. No se ha cobrado nada.
WHATSAPP_ONLY_FIELDSe envió un campo exclusivo de WhatsApp (reaction, contacts, location, sticker, context) en otro canal.
CONFLICTING_MESSAGE_TYPESUn mensaje lleva un solo tipo de contenido: reaction, contacts, location o sticker no se combinan con nada más.
CASCADE_NOT_SUPPORTEDcascade no es posible con una reacción, contactos, una ubicación o un sticker: no hay equivalente SMS.
INVALID_REACTIONreaction necesita el message_id (wamid) de destino y un solo emoji (o una cadena vacía para retirar la reacción).
INVALID_CONTEXTcontext necesita el message_id (wamid) del mensaje citado y no se combina con reaction.
INVALID_CONTACTScontacts debe ser un array de 1 a 20 fichas, cada una con name.formatted_name; el mensaje indica la ficha errónea.
INVALID_LOCATIONlocation exige latitude (-90 a 90) y longitude (-180 a 180) numéricas; name y address son opcionales.
INVALID_MEDIAUn medio de WhatsApp lleva "id" o "link" (URL https), nunca ambos; sin pie en audio y sticker.
META_WINDOW_EXPIREDFallo de WhatsApp (131047): la ventana de servicio de 24 horas estaba cerrada. Envíe una plantilla aprobada o un SMS.
META_UNDELIVERABLEFallo de WhatsApp (131026): el número no es localizable en WhatsApp. Sin reintento automático.
META_MARKETING_LIMITFallo de WhatsApp (131049): límite de mensajes de marketing por destinatario. No reenvíe de inmediato.
META_RATE_LIMITEDFallo de WhatsApp (130429 caudal del número, o 131056 como META_PAIR_RATE_LIMITED: demasiados mensajes a este contacto) tras 3 intentos automáticos.
META_POLICY_BLOCKEDFallo de WhatsApp (368; también META_ACCOUNT_LOCKED 131031, META_PAYMENT_ISSUE 131042, META_PHONE_NOT_REGISTERED 133010): número o cuenta emisora restringida. Revise WhatsApp Manager.
META_TEMPLATE_NOT_FOUNDFallo de WhatsApp (132001; también META_TEMPLATE_PARAM_MISMATCH 132000, META_TEMPLATE_PARAM_FORMAT 132012): plantilla inexistente en ese idioma o variables incorrectas.
RATE_LIMITEDLímite de peticiones por minuto superado. Respete la cabecera Retry-After.
INVALID_IDEMPOTENCY_KEYLa cabecera Idempotency-Key supera los 255 caracteres.
IDEMPOTENCY_IN_FLIGHTLa petición original con esta Idempotency-Key aún está en curso. Reintente en un momento.
IDEMPOTENCY_KEY_REUSEDEsta Idempotency-Key ya se usó con un cuerpo de petición diferente. Use una clave nueva.
INVALID_CHANNELEl canal solicitado no existe. Canales válidos: sms, whatsapp, telegram, voice, rcs.
CHANNEL_NOT_CONFIGUREDEl canal solicitado no está disponible actualmente en la plataforma.
CHANNEL_DISABLEDEl canal solicitado está desactivado en la plataforma.
FORBIDDENLa clave API no tiene el permiso necesario para esta acción, o la cuenta está suspendida.
SENDER_ID_TOO_LONGEl Sender ID supera el límite de 11 caracteres.
SENDER_ID_INVALIDEl Sender ID contiene caracteres no permitidos.
SENDER_ID_NOT_APPROVEDEl Sender ID aún no ha sido aprobado por los operadores.
SENDER_ID_PENDINGEl Sender ID está en proceso de aprobación.
SENDER_ID_REJECTEDEl Sender ID fue rechazado por los operadores.
SENDER_ID_GENERICEl Sender ID es un encabezado genérico (INFO, ALERTA, SERVICIO…), no una marca. Use su nombre de marca u omita sender_id.
SENDER_ID_PROTECTED_BRANDEl Sender ID designa o imita a una institución protegida (banco, operador, administración). Reservado a cuentas cuyo registro para ese nombre fue aprobado.
SENDER_ID_NOT_REGISTEREDEl Sender ID nunca fue registrado en esta cuenta y la cuenta aún no tiene una recarga: el envío se limita a su propio número verificado.
TRIAL_RESTRICTED_DESTINATIONCuenta en modo de prueba gratuita: los envíos de prueba están limitados a su propio número verificado. Realice una primera recarga para enviar a terceros.
MISSING_FIELDFalta un campo obligatorio en la petición.
CASCADE_TIMEOUTEl primer canal expiró; se cambia al canal secundario (cascada).
UPSTREAM_ERRORError de entrega a nivel del operador o de la pasarela.
OPTED_OUTEl número rechazó sus comunicaciones (STOP). Envío prohibido.
SPAM_OR_PHISHING_DETECTEDEl mensaje contiene un enlace identificado como phishing, o habla en nombre de un banco, operador o administración. Envío rechazado y cuenta suspendida pendiente de revisión.
CONTENT_BLOCKEDEl contenido del mensaje fue bloqueado por el control anti-abuso y la cuenta está suspendida pendiente de revisión. Contacte con soporte.
INVALID_CODEEl código OTP enviado es incorrecto. El mensaje de error indica cuántos intentos quedan.
EXPIRED_CODEEl código OTP ha caducado. Solicite uno nuevo mediante /v1/verify/send.
MAX_ATTEMPTSSe superó el número máximo de intentos de verificación. La sesión está cerrada.
STRIPE_ERRORNo se pudo crear la página de pago de nuestro lado. No se realizó ningún cargo — reintente en un momento.
INTERNAL_ERRORError interno de nuestro lado. Reintente; contacte con soporte si el problema persiste.
INVALID_PURPOSEpurpose debe ser marketing, transactional, otp o service.
INVALID_STATUSstatus debe ser granted o withdrawn.
INVALID_CHANNELchannel debe ser whatsapp, sms o any.
INVALID_SOURCEsource debe ser api, form, import o dashboard.
INVALID_EVIDENCEevidence debe ser un objeto JSON de menos de 4 KB.
INVALID_DATEUna fecha no está en formato ISO 8601, o recorded_at está en el futuro.
Límites
Restricciones y cuotas en el entorno de producción.
60 a 1 200 peticiones / minuto por clave según el plan (Starter 60, Business 180, Pro 500, Enterprise 1 200), ampliable bajo demanda.
1600 caracteres máximo por mensaje.
Hasta 10 000 mensajes por llamada API.
90 días para los informes detallados.