API REST y Webhooks de WhatsApp Hechos para Integraciones
Envíe plantillas y mensajes, sincronice contactos, etiquetas y conversaciones, y reciba un webhook firmado por cada respuesta y confirmación de entrega. REST simple en /api/v1 con claves de API con alcances, sobre la plataforma oficial WhatsApp Business. Sin SDK que instalar. La API está en todos los planes, desde Developer Free con 100 mensajes de API al mes.
✦ Prueba gratis de 14 días · Tu propio número de WhatsApp Business · Meta factura los mensajes directamente
Una API REST para Todo Su CRM de WhatsApp
Autentíquese con una clave de API del workspace (Authorization: Bearer sm_live_…) y llame a /api/v1. Envíe plantillas, texto, multimedia y mensajes interactivos; cree, actualice y etiquete contactos; lea etiquetas, etapas del embudo, plantillas y campañas; liste, asigne y resuelva conversaciones; administre endpoints de webhook; e informe ventas como conversiones. Toda respuesta exitosa usa el mismo envoltorio {data, meta}, y todo error un código estable sobre el que puede decidir.
- Claves creadas en Desarrolladores → Claves de API y mostradas una sola vez
- 10 alcances, con los modelos Acceso total, Solo envío, Solo lectura y Personalizado
- Meta factura los cargos de mensajes a su propia cuenta de WhatsApp Business; la API nunca cobra por mensaje
Webhooks Firmados en los que Puede Confiar
Suscriba un endpoint HTTPS a cualquiera de los 12 eventos: mensajes recibidos, enviados, entregados, leídos y fallidos; contactos creados, dados de baja y dados de alta; conversaciones asignadas y resueltas; cambios de estado de plantillas; y campañas finalizadas. Cada entrega se firma con HMAC-SHA256 en el encabezado X-SM-Signature, se reintenta tras 1m, 5m, 30m, 2h, 6h y 12h y aparece en un registro de entregas desde donde puede reenviarla.
- Entregado al menos una vez: elimine duplicados por el id del evento
- Los endpoints que fallan continuamente (50 veces seguidas o durante 24 horas) se desactivan y se señalan
- Rote el secreto de firma con 24 horas de superposición
Los Detalles de los que Dependen las Integraciones
-
Envíos idempotentes
Envíe un Idempotency-Key en un POST. La misma clave y el mismo cuerpo dentro de 24 horas devuelven la respuesta original con Idempotent-Replayed: true, así un reintento nunca envía dos veces.
-
Paginación por cursor y sincronización
Las listas aceptan limit (1–100) y cursor y devuelven nextCursor. En contactos y conversaciones, updatedSince devuelve solo lo que cambió, del más antiguo al más reciente, para sincronización incremental.
-
Sus propias referencias
Agregue un clientReference, como el id de su pedido, a un envío. Vuelve en la respuesta, en GET /messages/{id} y en cada webhook de enviado, entregado, leído y fallido.
-
Claves con privilegio mínimo
Dé a cada integración su propia clave, solo con los alcances que necesita. Un webhook creado con una clave recibe una familia de eventos solo mientras esa clave tenga el alcance de lectura correspondiente.
-
Registros de solicitudes y entregas
Vea en el panel las últimas 500 solicitudes a la API de los últimos 7 días, filtradas por clave o solo errores, y reenvíe cualquier webhook desde el registro de entregas de su endpoint.
-
Referencia OpenAPI
Un documento OpenAPI JSON en /api/v1/openapi.json y Swagger UI en /api/v1/docs, con el alcance de cada operación indicado como x-scope. La misma referencia está en el panel, en Desarrolladores.
Lo que Cubre la API
Todas las rutas están bajo /api/v1. Por ahora, crear plantillas y campañas solo se hace desde el panel: la API las lee y envía plantillas aprobadas un mensaje a la vez.
| Recurso | Endpoints | Alcance | Qué hace |
|---|---|---|---|
| Cuenta | GET /me, GET /phone-numbers | Cualquier clave válida | Workspace, plan, alcances de la clave, límites y uso en vivo; sus números con calificación de calidad y límite de mensajes |
| Mensajes | POST /messages, GET /messages/{id} | messages:send, messages:read | Mensajes de plantilla (incluidos carruseles y ofertas por tiempo limitado), texto, imagen, video, audio, documento e interactivos (los interactivos requieren Team o Business); estado, motivo de fallo y categoría de precio de Meta |
| Contactos | GET/POST /contacts, GET/PATCH/DELETE /contacts/{idOrPhone} | contacts:read, contacts:write | Crear o actualizar por número de teléfono, etiquetas, atributos personalizados, alta y baja; poner un contacto en una etapa del embudo requiere Business |
| Etiquetas y embudo | GET /tags, GET /pipeline-stages | contacts:read | Etiquetas con número de contactos; las etapas de su embudo de leads |
| Plantillas | GET /templates, GET /templates/{id} | templates:read | Solo lectura: las plantillas se crean en el panel y Meta las revisa |
| Conversaciones | GET /conversations, GET /conversations/{id}, GET /conversations/{id}/messages, PATCH /conversations/{id} | conversations:read, conversations:write | Historial de mensajes y windowOpen; asignar, desasignar, resolver y reabrir (la lectura funciona en todos los planes; los cambios requieren Team o Business) |
| Campañas | GET /campaigns, GET /campaigns/{id} | campaigns:read | Estadísticas de solo lectura; las campañas se crean y lanzan en el panel |
| Conversiones | POST /conversions | conversions:write | Informe una venta para que la campaña que trajo al cliente reciba el crédito (Business) |
| Webhooks | GET/POST /webhooks, DELETE /webhooks/{id} | webhooks:manage | Suscribir y cancelar endpoints (REST hooks) |
Límites de la API por Plan
Se cuentan por workspace, no por clave. Las respuestas contadas incluyen los encabezados RateLimit-* y todo 429 incluye Retry-After. Los mensajes de API al mes cuentan solo lo enviado por la API; superada la cuota, un envío recibe 402 PLAN_MESSAGE_LIMIT_REACHED. El límite de mensajes propio de Meta para su número sigue aplicando.
| Plan | Solicitudes / minuto | Envíos por API / hora | Mensajes de API / mes | Endpoints de webhook |
|---|---|---|---|---|
| Prueba gratis (14 días) | 300 | 100 | 1,000 | 5 |
| Developer Free | 60 | 2,000 | 100 | 2 |
| Developer 1K | 120 | 10,000 | 1,000 | 2 |
| Developer 10K | 300 | 10,000 | 10,000 | 2 |
| Developer 100K | 1,200 | 10,000 | 100,000 | 2 |
| Team | 300 | 10,000 | Ilimitado (uso razonable) | 1 |
| Business | 1,200 | 10,000 | Ilimitado (uso razonable) | 5 |
¿Solo la API? Elija un tamaño Developer
Los planes Developer son la API con un panel de solo lectura: 1 puesto, 2 endpoints de webhook y los mensajes de API al mes que se muestran aquí. Los precios incluyen GST, y un año cuesta diez meses. Los tamaños de pago nunca detienen las plantillas de autenticación.
| Plan | Precio / mes | Mensajes de API / mes | Solicitudes / minuto |
|---|---|---|---|
| Developer Free | ₹0 | 100 | 60 |
| Developer 1K | ₹100 | 1,000 | 120 |
| Developer 10K | ₹499 | 10,000 | 300 |
| Developer 100K | ₹999 | 100,000 | 1,200 |
Verifique su clave y envíe una plantilla
GET /me no requiere alcance. El envío requiere messages:send y una plantilla aprobada.
# 1. Check your key: workspace, plan, scopes, limits and usage
curl https://waba-api.startmessaging.com/api/v1/me \
-H "Authorization: Bearer sm_live_your_api_key"
# 2. Send an approved template, safe to retry with the same Idempotency-Key
curl -X POST https://waba-api.startmessaging.com/api/v1/messages \
-H "Authorization: Bearer sm_live_your_api_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-10421-ready" \
-d '{
"to": "+919812345678",
"type": "template",
"template": {
"name": "order_ready_for_pickup",
"language": "en_US",
"parameters": [
{ "text": "Asha" },
{ "text": "10421" }
]
},
"clientReference": "order-10421"
}' const API = 'https://waba-api.startmessaging.com/api/v1';
const auth = { Authorization: 'Bearer sm_live_your_api_key' };
// 1. Check your key
const me = await fetch(`${API}/me`, { headers: auth }).then((r) => r.json());
console.log(me.data.plan.code, me.data.apiKey.scopes, me.data.limits);
// 2. Send an approved template, safe to retry with the same Idempotency-Key
const res = await fetch(`${API}/messages`, {
method: 'POST',
headers: {
...auth,
'Content-Type': 'application/json',
'Idempotency-Key': 'order-10421-ready',
},
body: JSON.stringify({
"to": "+919812345678",
"type": "template",
"template": {
"name": "order_ready_for_pickup",
"language": "en_US",
"parameters": [
{ "text": "Asha" },
{ "text": "10421" }
]
},
"clientReference": "order-10421"
}),
});
const { data } = await res.json();
console.log(data.id, data.status, res.headers.get('Idempotent-Replayed')); import requests
API = 'https://waba-api.startmessaging.com/api/v1'
AUTH = {'Authorization': 'Bearer sm_live_your_api_key'}
# 1. Check your key
me = requests.get(f'{API}/me', headers=AUTH).json()['data']
print(me['plan']['code'], me['apiKey']['scopes'], me['limits'])
# 2. Send an approved template, safe to retry with the same Idempotency-Key
res = requests.post(
f'{API}/messages',
headers={**AUTH, 'Idempotency-Key': 'order-10421-ready'},
json={
"to": "+919812345678",
"type": "template",
"template": {
"name": "order_ready_for_pickup",
"language": "en_US",
"parameters": [
{ "text": "Asha" },
{ "text": "10421" }
]
},
"clientReference": "order-10421"
},
)
data = res.json()['data']
print(data['id'], data['status']) <?php
$api = 'https://waba-api.startmessaging.com/api/v1';
$auth = 'Authorization: Bearer sm_live_your_api_key';
// 1. Check your key
$ch = curl_init("$api/me");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [$auth],
]);
$me = json_decode(curl_exec($ch), true)['data'];
echo $me['plan']['code'] . ' ' . implode(',', $me['apiKey']['scopes']) . "\n";
// 2. Send an approved template, safe to retry with the same Idempotency-Key
$body = <<<'JSON'
{
"to": "+919812345678",
"type": "template",
"template": {
"name": "order_ready_for_pickup",
"language": "en_US",
"parameters": [
{ "text": "Asha" },
{ "text": "10421" }
]
},
"clientReference": "order-10421"
}
JSON;
$ch = curl_init("$api/messages");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
$auth,
'Content-Type: application/json',
'Idempotency-Key: order-10421-ready',
],
CURLOPT_POSTFIELDS => $body,
]);
$data = json_decode(curl_exec($ch), true)['data'];
echo $data['id'] . ' ' . $data['status']; Verifique cada webhook antes de confiar en él
X-SM-Signature es v1=HMAC-SHA256(secret, timestamp + "." + cuerpo sin procesar). Use el secreto whsec_ completo como clave.
import express from 'express';
import { createHmac, timingSafeEqual } from 'node:crypto';
const app = express();
const SECRET = process.env.SM_WEBHOOK_SECRET; // the whole whsec_… string
function verify(rawBody, headers) {
const ts = headers['x-sm-timestamp'];
const sigs = headers['x-sm-signature'];
if (!/^\d+$/.test(ts ?? '') || typeof sigs !== 'string') return false;
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // 5-minute tolerance
const expected = createHmac('sha256', SECRET).update(`${ts}.`).update(rawBody).digest();
return sigs.split(',').some((part) => {
const m = /^v1=([0-9a-f]{64})$/.exec(part.trim());
return m !== null && timingSafeEqual(Buffer.from(m[1], 'hex'), expected);
});
}
app.post('/webhooks/startmessaging', express.raw({ type: 'application/json' }), (req, res) => {
if (!verify(req.body, req.headers)) return res.sendStatus(401);
res.sendStatus(200); // answer 2xx within 5 seconds, then do the work
const event = JSON.parse(req.body);
// Delivered at least once: skip event.id if you have already handled it.
if (event.type === 'message.delivered') {
console.log(event.data.message.clientReference, 'delivered');
}
}); import hmac, hashlib, json, os, time
from flask import Flask, request
app = Flask(__name__)
SECRET = os.environ['SM_WEBHOOK_SECRET'].encode() # the whole whsec_… string
def verify(raw_body, ts, sigs):
if not ts or not ts.isdigit() or not sigs:
return False
if abs(time.time() - int(ts)) > 300: # 5-minute tolerance
return False
expected = hmac.new(SECRET, f'{ts}.'.encode() + raw_body, hashlib.sha256).hexdigest()
return any(
hmac.compare_digest(part.strip()[3:], expected)
for part in sigs.split(',') if part.strip().startswith('v1=')
)
@app.post('/webhooks/startmessaging')
def startmessaging_webhook():
raw = request.get_data()
if not verify(raw, request.headers.get('X-SM-Timestamp'), request.headers.get('X-SM-Signature')):
return '', 401
event = json.loads(raw)
# Delivered at least once: skip event['id'] if you have already handled it.
if event['type'] == 'message.delivered':
print(event['data']['message']['clientReference'], 'delivered')
return '', 200 <?php
$secret = getenv('SM_WEBHOOK_SECRET'); // the whole whsec_… string
$raw = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_SM_TIMESTAMP'] ?? '';
$sigs = $_SERVER['HTTP_X_SM_SIGNATURE'] ?? '';
function sm_verify(string $raw, string $ts, string $sigs, string $secret): bool
{
if (!ctype_digit($ts) || $sigs === '') return false;
if (abs(time() - (int) $ts) > 300) return false; // 5-minute tolerance
$expected = hash_hmac('sha256', $ts . '.' . $raw, $secret);
foreach (explode(',', $sigs) as $part) {
$part = trim($part);
if (strncmp($part, 'v1=', 3) === 0 && hash_equals($expected, substr($part, 3))) {
return true;
}
}
return false;
}
if (!sm_verify($raw, $ts, $sigs, $secret)) {
http_response_code(401);
exit;
}
http_response_code(200); // answer 2xx within 5 seconds
$event = json_decode($raw, true);
// Delivered at least once: skip $event['id'] if you have already handled it.
if ($event['type'] === 'message.delivered') {
error_log($event['data']['message']['clientReference'] . ' delivered');
} Informe una venta y acredite la campaña
En el plan Business, POST /api/v1/conversions acredita la venta a la última campaña entregada a ese número dentro de su ventana de conversión (7 días salvo que la cambie), y el informe de la campaña suma el valor.
# Business plan, scope conversions:write
curl -X POST https://waba-api.startmessaging.com/api/v1/conversions \
-H "Authorization: Bearer sm_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"phone": "+919812345678",
"orderId": "ORD-10442",
"value": 2499.5,
"currency": "INR",
"occurredAt": "2026-10-12T10:30:00+05:30"
}'
# 201 = recorded now, 200 = this orderId was reported already const res = await fetch('https://waba-api.startmessaging.com/api/v1/conversions', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + process.env.SM_API_KEY, // sm_live_your_api_key
'Content-Type': 'application/json',
},
body: JSON.stringify({
phone: '+919812345678',
orderId: 'ORD-10442', // the same orderId again never counts the sale twice
value: 2499.5,
currency: 'INR',
}),
});
const { data } = await res.json();
console.log(data.created, data.conversion.campaign?.name ?? 'no campaign credited'); import os, requests
res = requests.post(
'https://waba-api.startmessaging.com/api/v1/conversions',
headers={'Authorization': f"Bearer {os.environ['SM_API_KEY']}"}, # sm_live_your_api_key
json={
'phone': '+919812345678',
'orderId': 'ORD-10442',
'value': 2499.5,
'currency': 'INR',
},
)
data = res.json()['data']
campaign = data['conversion']['campaign']
print(data['created'], campaign['name'] if campaign else 'no campaign credited') API de WhatsApp — Preguntas Frecuentes
¿Hay un SDK?
No. La API es REST simple sobre HTTPS con JSON, así que funciona cualquier lenguaje o herramienta que pueda hacer una solicitud HTTPS. La documentación muestra cURL, Node.js, Python y PHP, y el documento OpenAPI describe cada campo de solicitud y respuesta.
¿Qué planes incluyen la API?
Todos los planes, incluido Developer Free. Los planes Developer son la API con un panel de solo lectura, dimensionados por mensajes de API al mes: Developer Free con 100, luego 1K, 10K y 100K. Team y Business añaden la bandeja de entrada compartida, los contactos y las campañas, sin límite mensual de mensajes de API dentro de un uso razonable. La prueba gratis de 14 días incluye la API con 1,000 mensajes de API. Las solicitudes por minuto y los endpoints de webhook dependen del plan, y GET /me muestra su plan, los límites y el conteo del mes.
¿Puedo crear plantillas o campañas a través de la API?
Todavía no. Las plantillas se crean en el panel y Meta las revisa; las campañas se arman y lanzan con el asistente de campañas del panel. La API lee ambas, envía plantillas aprobadas un mensaje a la vez con POST /messages y le avisa cuando una campaña termina con el webhook campaign.completed.
¿La API puede enviar carruseles y ofertas por tiempo limitado?
Sí. Para una plantilla de carrusel, envíe template.carouselCards con una entrada por tarjeta, en orden, cada una con su headerMediaUrl (https) y, opcionalmente, parameters y buttonParameters. Para una oferta por tiempo limitado, defina template.offerExpiresAt con una hora ISO 8601 futura, y WhatsApp muestra al cliente una cuenta regresiva. Las campañas no envían estas plantillas, así que salen una a una por la API o la bandeja de entrada.
¿Qué pasa cuando se agota la cuota mensual de mensajes de API?
Los planes Developer y la prueba gratis incluyen una cantidad de mensajes de API al mes, contada desde el día 1; solo cuentan los mensajes enviados por la API. Developer Free y la prueba se detienen en su cuota. Un plan Developer de pago detiene los demás mensajes al triple de su tamaño y nunca detiene las plantillas de autenticación. Un envío que supera el límite recibe 402 PLAN_MESSAGE_LIMIT_REACHED con sent, stopAt y resetsAt en los detalles, y GET /me muestra el conteo antes de que ocurra. Team y Business no tienen límite mensual de mensajes de API (uso razonable).
¿Cómo conecto Zapier, Make o Pabbly Connect?
Todavía no hay apps oficiales de StartMessaging en esos marketplaces. Use sus módulos genéricos: un disparador de catch-hook o webhook personalizado para recibir eventos y una acción de solicitud HTTP con su clave de API para llamar a la API. Quien cree su propia app puede suscribirse y cancelar con POST /webhooks y DELETE /webhooks/{id}.
¿Qué pasa si reintento un envío?
Con el mismo Idempotency-Key y el mismo cuerpo dentro de 24 horas, recibe la respuesta original con Idempotent-Replayed: true, y nada se envía dos veces. Reutilizar una clave con un cuerpo distinto se rechaza con IDEMPOTENCY_KEY_REUSED. Tras un 429 o un 5xx, reintente con la misma clave.
¿Cómo funcionan los límites de uso?
Los límites son por workspace, así que más claves nunca suman capacidad. Solicitudes por minuto: 60 en Developer Free, 120 en Developer 1K, 300 en Developer 10K, Team y la prueba gratis, y 1,200 en Developer 100K y Business. Envíos por API por hora: 10,000 en los planes de pago, 2,000 en Developer Free y 100 en la prueba. Un envío cuenta en ambos límites, y un envío rechazado devuelve su cupo por hora. Un 429 le indica en Retry-After cuánto esperar. El límite de mensajes propio de Meta para su número se aplica además.
¿StartMessaging cobra por mensaje enviado por la API?
No. StartMessaging es Meta Tech Provider: usted conecta su propia Cuenta de WhatsApp Business, y Meta le cobra los mensajes directamente a ella, con las tarifas de Meta. Meta cobra cada plantilla entregada, incluidas las de utilidad aun dentro de la ventana de 24 horas, y las respuestas de atención más allá de una cuota mensual gratuita. StartMessaging cobra un plan fijo, sin billetera y sin recargo por mensaje; los planes Developer se dimensionan por cuántos mensajes de API al mes incluyen. El campo pricing de cada mensaje muestra cómo lo clasificó Meta.
Cree Su Integración con WhatsApp
Su primer workspace obtiene una prueba gratis de 14 días; después, Developer Free mantiene la API activa con 100 mensajes al mes. Conecte su propio número de WhatsApp Business, cree una clave y haga su primera llamada.