API para Desarrolladores

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
Abrir la referencia de la API →
API REST de WhatsApp de StartMessaging

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
Lea la guía de webhooks →
Eventos de webhook de WhatsApp firmados
Hecha para Producció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.

Endpoints

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 Uso

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
Planes Developer

¿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
Inicio Rápido

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'];
Webhooks

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');
}
Conversiones

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')
Preguntas Frecuentes

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.

WhatsApp API

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.