WhatsApp API, 1 de octubre: qué cambia en tu código cuando Meta cobra los mensajes de servicio
Tabla de Contenidos
El 1 de octubre de 2026 el webhook de estados de WhatsApp no cambia de forma: cambia de significado. El mismo objeto pricing que hoy llega con "type": "free_customer_service" empieza a llegar con "type": "regular" en dos casos que hasta el 30 de septiembre son gratis: los mensajes de servicio a partir del 1,001 del mes en cada número, y las plantillas de utilidad que mandas dentro de la ventana de 24 horas. Si tu código tiene la tarifa de servicio fija en cero, decide con billable o descarta los estados cuya categoría no reconoce, tu reporte va a decir que no gastaste nada mientras Meta te cobra.
De precios en español ya se escribió mucho. Este post va de lo otro: qué campos mirar, qué valores trae cada uno según la documentación de Meta, cómo contar el cupo gratis sin inventarte la regla y qué revisar antes del 30 de septiembre. Todas las cifras son de la tarjeta de México en pesos.
Qué se cobra antes y después del 1 de octubre
Tarifas por mensaje entregado a un número con lada +52, en MXN. Salen de las tarjetas oficiales «effective July 1, 2026» y «effective October 1, 2026», que descargué el 13 de septiembre desde la página de precios de Meta (fila Mexico).
| Mensaje | Hasta el 30-sep | Desde el 1-oct | pricing.type desde el 1-oct |
|---|---|---|---|
| Plantilla de marketing | 0.5614 | 0.7298 | regular |
| Plantilla de utilidad fuera de la ventana | 0.1565 | 0.1565 | regular |
| Plantilla de utilidad dentro de la ventana de 24 h | gratis | 0.1565, desde la primera | regular |
| Plantilla de autenticación | 0.1565 | 0.1565 | regular |
| Servicio (sin plantilla), del 1 al 1,000 del mes por número | gratis | gratis | free_customer_service |
| Servicio (sin plantilla), del 1,001 en adelante | gratis | 0.1565 | regular |
| Cualquiera dentro de la ventana de punto de entrada gratuito (72 h) | gratis | gratis | free_entry_point |
Tres detalles que cambian cómo lo programas:
- El cambio entra a las 00:00 en la zona horaria de tu WABA, no en UTC. La página de precios lo dice para todo el paquete de octubre: «Rate updates below apply as of 12am by WhatsApp Business Account (WABA) timezone».
- El cupo de 1,000 es solo de servicio. Meta lo describe como «a free monthly tier of 1,000 service messages per business phone number», que no se acumula de un mes a otro. No anuncia ningún cupo para utility: una plantilla de utilidad dentro de la ventana se cobra desde la primera.
- El marketing sube casi 30 % en México (0.5614 → 0.7298). Es el único cambio de tarifa de la fila; utility, autenticación y servicio quedan en 0.1565. Y los niveles por volumen no te van a salvar: en la tarjeta de niveles de octubre, la utility de México se cobra a precio de lista del mensaje 0 al 1,000,000 del mes, y el servicio no tiene niveles en absoluto según la página de mensajes sin plantilla.
El payload real y los campos que importan
Así llega un mensaje de servicio entregado después de agotar el cupo del mes. La estructura es la de la referencia del webhook de estados y el objeto pricing es literal el que publica Meta para «Paid service message». Los IDs y teléfonos son los de ejemplo de la propia referencia; el timestamp es el 14 de octubre de 2026.
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "102290129340398",
"changes": [
{
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "15550783881",
"phone_number_id": "106540352242922"
},
"statuses": [
{
"id": "wamid.HBgLMTY1MDM4Nzk0MzkVAgASGBQzQUFERjg0NDEzNDdFODU3MUMxMAA=",
"status": "delivered",
"timestamp": "1792002600",
"recipient_id": "16505551234",
"pricing": {
"billable": true,
"pricing_model": "PMP",
"type": "regular",
"category": "service"
}
}
]
},
"field": "messages"
}
]
}
]
}
El mismo mensaje, en septiembre o dentro del cupo, trae "billable": false y "type": "free_customer_service". Una utility dentro de la ventana pasa exactamente igual: free_customer_service hasta el 30 de septiembre, regular desde el 1 de octubre, con "category": "utility".
pricing.type: el que decide
Es el campo que dice por qué un mensaje se cobra o no. La referencia de estados documenta tres valores:
regular: se cobra.free_customer_service: gratis por ir dentro de la ventana de atención. Desde octubre, eso incluye estar dentro del cupo de 1,000.free_entry_point: gratis por la ventana de 72 horas que abren los anuncios Click-to-WhatsApp y los botones de llamada a la acción de una página de Facebook.
La página de precios añade un cuarto que la referencia (actualizada el 21 de mayo) todavía no lista: free_group_customer_service, para mensajes a grupos.
pricing.category: qué tarifa se aplicó
Valores de la referencia: authentication, authentication-international, marketing, marketing_lite, referral_conversion, service y utility. Ojo con authentication-international: en pricing.category lleva guion, mientras que la categoría de conversation.origin.type lleva guion bajo. La página de precios añade group_service y group_utility para grupos.
La consecuencia práctica: no valides estos campos contra una lista cerrada. Si tu parser descarta un estado porque la categoría no está en tu enum, ese mensaje se queda sin precio en tu base mientras Meta sí lo cobra.
pricing.billable: no decidas con él
La referencia es explícita: «The billable property will be deprecated in a future versioned release. Use pricing.type and pricing.category together to determine whether a message is billable and, if so, its billing rate.» Hoy billable y type coinciden. El día que Meta lo quite en una versión nueva de la Graph API, un if (billable === false) return 0 se convierte en «todo cuesta» o en «nada cuesta», según cómo trates el undefined.
Cuándo llega el objeto, y cuándo cuenta
- Según la sintaxis de la referencia,
pricingviene «only included with sent status, and one of either delivered or read status». El ejemplo de la v24 aclara que puede venir solo en eldeliveredy no en elsent. Así que te va a llegar dos veces o una, y tienes que deduplicar porwamid. - Meta cobra lo entregado, no lo enviado («only when the message is delivered (vs. sent)»). Y el
deliveredpuede no llegar nunca: si el usuario tiene el chat abierto, Meta manda directo elread. Cuenta un mensaje como entregado cuando llegadeliveredoread. - Desde la v24.0 el objeto
conversationya no viene, salvo dentro de una ventana de punto de entrada gratuito. Si tu código sacaba la categoría deconversation.origin.type, ya iba tarde.
Los cuatro errores que hacen que tu reporte diga cero
Revisando para este post el agente de WhatsApp que tengo en desarrollo, encontré dos de estos en mi propio código. Los cuatro son fáciles de tener:
- Tarifa sin fecha de vigencia. Una constante
service: 0calcula bien hasta el 30 de septiembre y mal desde el 1 de octubre, aunque el webhook ya digaregular. Meta solo puede cambiar tarifas el día 1 de cada trimestre (enero, abril, julio y octubre), con un mes de aviso para una actualización de tarjeta: modela la tarjeta con su fecha de entrada en vigor. - Decidir con
billable. Funciona hoy; deja de funcionar cuando Meta lo retire. - Lista blanca de categorías.
group_service,free_group_customer_serviceo el guion deauthentication-internationalacaban como «sin categoría» y valen cero en tu suma. - Contar cada estado. Si sumas en cada webhook con
pricing, un mismo mensaje cuenta doble (sent+delivered), y un mensaje que falló después de enviarse cuenta aunque Meta no lo cobre.
Código: contar lo facturable por número y por mes
La idea es la del libro de costos de mi agente: una fila por mensaje saliente, que se va rellenando con lo que traiga cada estado, y el costo se calcula al leer. Está escrito para Cloudflare D1 (SQLite), pero el SQL es portable.
Primero la tabla. category y pricing_type se guardan tal cual llegan, sin lista blanca:
CREATE TABLE wa_outbound (
wamid TEXT PRIMARY KEY,
phone_number_id TEXT NOT NULL,
month TEXT NOT NULL, -- 'YYYY-MM' en la zona horaria de la WABA
delivered INTEGER NOT NULL DEFAULT 0, -- 1 cuando llegó `delivered` o `read`
category TEXT, -- pricing.category, sin normalizar
pricing_type TEXT -- pricing.type, sin normalizar
);
CREATE INDEX wa_outbound_month ON wa_outbound (phone_number_id, month);
Después, lo que haces con cada statuses[]. El phone_number_id sale de value.metadata.phone_number_id, que es la unidad del cupo:
type Open<T extends string> = T | (string & {}); // autocompleta, pero acepta lo que Meta añada
export interface StatusPricing {
/** Meta lo va a deprecar: decide con `type` + `category`. */
billable?: boolean;
pricing_model: Open<'PMP' | 'CBP'>;
type: Open<'regular' | 'free_customer_service' | 'free_entry_point' | 'free_group_customer_service'>;
category: Open<'service' | 'utility' | 'marketing' | 'marketing_lite' | 'authentication' | 'authentication-international' | 'referral_conversion' | 'group_service' | 'group_utility'>;
}
export interface WebhookStatus {
id: string; // wamid
status: Open<'sent' | 'delivered' | 'read' | 'failed' | 'played'>;
timestamp: string; // epoch en segundos, como cadena
recipient_id: string;
pricing?: StatusPricing;
}
/** 'YYYY-MM' en la zona horaria de tu WABA, p. ej. 'America/Mexico_City'. */
export function monthKey(epochSeconds: number, timeZone: string): string {
return new Intl.DateTimeFormat('en-CA', { timeZone, year: 'numeric', month: '2-digit' })
.format(new Date(epochSeconds * 1000))
.slice(0, 7);
}
const DELIVERED = new Set(['delivered', 'read']); // si llega `read` sin `delivered`, se entregó
export async function applyStatus(db: D1Database, phoneNumberId: string, s: WebhookStatus, timeZone: string) {
await db
.prepare(
`INSERT INTO wa_outbound (wamid, phone_number_id, month, delivered, category, pricing_type)
VALUES (?1, ?2, ?3, ?4, ?5, ?6)
ON CONFLICT(wamid) DO UPDATE SET
month = CASE WHEN excluded.delivered = 1 AND delivered = 0 THEN excluded.month ELSE month END,
delivered = MAX(delivered, excluded.delivered),
category = COALESCE(excluded.category, category),
pricing_type = COALESCE(excluded.pricing_type, pricing_type)`,
)
.bind(
s.id,
phoneNumberId,
monthKey(Number(s.timestamp), timeZone),
DELIVERED.has(s.status) ? 1 : 0,
s.pricing?.category ?? null,
s.pricing?.type ?? null,
)
.run();
}
El ON CONFLICT hace el trabajo sucio: los estados repetidos no duplican, el pricing se conserva venga en el sent o en el delivered, y el mes que cuenta es el de la entrega (un mensaje enviado el 30 de septiembre a las 23:59 y entregado el 1 de octubre cae en octubre).
Por último, la cuenta del mes. La tarjeta lleva fecha, y lo que decide si se cobra es type, no tu contador:
const FREE_SERVICE_PER_MONTH = 1000;
/** Tarjeta de México en MXN por mensaje entregado. Una entrada por trimestre con cambios. */
const MX_RATES_MXN: Array<{ from: string; rates: Record<string, number> }> = [
{ from: '2026-07', rates: { marketing: 0.5614, utility: 0.1565, authentication: 0.1565 } },
{ from: '2026-10', rates: { marketing: 0.7298, utility: 0.1565, authentication: 0.1565, service: 0.1565 } },
];
const ratesFor = (month: string) => MX_RATES_MXN.findLast((card) => card.from <= month)?.rates ?? {};
export async function monthlyBill(db: D1Database, phoneNumberId: string, month: string) {
const { results } = await db
.prepare(
`SELECT category, pricing_type, COUNT(*) AS n FROM wa_outbound
WHERE phone_number_id = ? AND month = ? AND delivered = 1
GROUP BY category, pricing_type`,
)
.bind(phoneNumberId, month)
.all<{ category: string | null; pricing_type: string | null; n: number }>();
const rates = ratesFor(month);
let mxn = 0;
let serviceDelivered = 0;
let paidService = 0;
const unpriced: Record<string, number> = {};
for (const { category, pricing_type, n } of results) {
if (!category || !pricing_type) {
unpriced['(sin pricing)'] = (unpriced['(sin pricing)'] ?? 0) + n;
continue;
}
// Meta no documenta si los de punto de entrada gastan cupo: aquí no se cuentan.
if (category === 'service' && pricing_type !== 'free_entry_point') serviceDelivered += n;
if (pricing_type !== 'regular') continue; // gratis: ventana, cupo o punto de entrada
if (category === 'service') paidService += n;
const rate = rates[category];
if (rate === undefined) {
unpriced[category] = (unpriced[category] ?? 0) + n; // se cobra y no tienes tarifa: no lo sumes como 0
continue;
}
mxn += n * rate;
}
return {
mxn: Math.round(mxn * 100) / 100,
serviceDelivered,
freeServiceLeft: Math.max(0, FREE_SERVICE_PER_MONTH - serviceDelivered),
paidService,
unpriced,
};
}
Con eso, la alerta es una línea en un cron diario: avisa cuando freeServiceLeft baje de 200, cuando aparezca el primer paidService del mes o cuando unpriced no venga vacío. Y si paidService es mayor que cero con serviceDelivered por debajo de 1,000, tu contador y Meta no coinciden: puede ser otra aplicación mandando desde el mismo número, webhooks perdidos o el corte de mes en otra zona horaria. Manda Meta.
Para cruzarlo a fin de mes, el campo pricing_analytics de la WABA acepta las dimensiones PHONE, PRICING_CATEGORY y PRICING_TYPE. Dos avisos de la misma página: los datos son aproximados («may differ from what’s shown on invoices»), y COST no viene si tu WABA usa la línea de crédito de un proveedor. Además, la descripción de SERVICE en esa referencia (actualizada el 11 de junio) todavía dice «Messages that were not charged»; la página de mensajes sin plantilla ya muestra la consulta con REGULAR + SERVICE.
Cuánto cuesta en pesos: una PyME con 600 conversaciones al mes
Un consultorio o un taller con un agente que atiende WhatsApp, un solo número, clientes con lada +52 y sin anuncios Click-to-WhatsApp. Al mes:
- 600 conversaciones, con 5 mensajes de servicio del agente cada una: 3,000 mensajes de servicio.
- 600 confirmaciones con plantilla de utilidad, mandadas dentro de la ventana.
- 400 recordatorios con plantilla de utilidad, fuera de la ventana.
- Una campaña de marketing a 500 contactos.
Supongo que todo se entrega; en la vida real sale un poco menos, porque Meta solo cobra lo entregado. Tarifas de lista, sin impuestos.
Septiembre (tarjeta de julio):
| Concepto | Cuenta | MXN |
|---|---|---|
| Servicio | 3,000 × 0 | 0.00 |
| Utility dentro de la ventana | 600 × 0 | 0.00 |
| Utility fuera de la ventana | 400 × 0.1565 | 62.60 |
| Marketing | 500 × 0.5614 | 280.70 |
| Total | 343.30 |
Octubre (tarjeta de octubre):
| Concepto | Cuenta | MXN |
|---|---|---|
| Servicio | (3,000 − 1,000) × 0.1565 = 2,000 × 0.1565 | 313.00 |
| Utility dentro de la ventana | 600 × 0.1565 | 93.90 |
| Utility fuera de la ventana | 400 × 0.1565 | 62.60 |
| Marketing | 500 × 0.7298 | 364.90 |
| Total | 834.40 |
La factura de Meta pasa de 343.30 a 834.40 pesos: 491.10 MXN más al mes. No es una cifra que quiebre a nadie, pero es 2.4 veces la de septiembre, y 406.90 de esos 491.10 pesos (más del 80 %) salen de dos líneas que hoy valen cero.
Dónde está la palanca de verdad: en tu código
Pasar las confirmaciones de plantilla de utilidad a texto libre dentro de la ventana no ahorra nada en este ejemplo: la tarifa de servicio y la de utilidad son la misma en México (0.1565) y el cupo de 1,000 ya se agotó. Solo ayuda a negocios que se quedan por debajo de 1,000 mensajes de servicio al mes.
Lo que sí mueve la cifra es cuántos mensajes manda tu agente por respuesta. Muchos agentes de IA parten cada respuesta en tres o cuatro burbujas porque se lee más natural. Desde el 1 de octubre, cada burbuja pasado el mensaje 1,000 es un cargo. Si el mismo agente contesta con 3 mensajes por conversación en vez de 5:
- 600 × 3 = 1,800 mensajes de servicio.
- (1,800 − 1,000) × 0.1565 = 800 × 0.1565 = 125.20 MXN, contra 313.00.
- Ahorro: 187.80 MXN al mes, sin tocar un solo precio.
Si parte de tus clientes llega por anuncios Click-to-WhatsApp, responder dentro de las primeras 24 horas abre la ventana de punto de entrada de 72 horas, y ahí todo sale como free_entry_point. Meta confirma que esa ventana no cambia para la entrega de mensajes.
Qué pasa si no tienes método de pago
Aquí Meta tiene dos redacciones, de fechas distintas, y conviene leerlas juntas.
La página de mensajes sin plantilla, actualizada el 25 de agosto de 2026, dice:
For any Solution Provider or directly-integrated businesses that does not have a payment method on file by September 30, 2026, Meta will stop delivering service messages as of when they become charged on October 1, 2026. To avoid disruptions to your service messages, please add a payment method for your WhatsApp Business Account(s) by September 30, 2026.
La página de precios, actualizada el 10 de septiembre de 2026, que es la que introduce el cupo gratis como novedad, precisa:
If you do not have a payment method for your WhatsApp Business account: Meta will deliver your first 1,000 service messages each month but not deliver as of your 1,001st.
No se contradicen: la primera dice que se dejan de entregar «cuando empiezan a cobrarse», y la segunda, escrita después del cupo, dice que eso ocurre en el mensaje 1,001. En la práctica:
- La fecha que da Meta para tener el método de pago es el 30 de septiembre. No esperes a ver el primer fallo.
- Sin método de pago, tu agente funciona normal hasta que el número cruza los 1,000 mensajes de servicio del mes, y ahí deja de entregar a mitad de mes. En un negocio con volumen, eso puede ser un martes a media tarde.
- Meta no publica, en las páginas que revisé, qué código de error trae el estado
faileden ese caso. Vigila un aumento defaileden mensajes de servicio desde el 1 de octubre. - El aviso de agosto va dirigido a «Solution Provider or directly-integrated businesses». Si tu WABA factura a través de un proveedor de soluciones, pídele por escrito que confirme que el método de pago está resuelto antes del 30 de septiembre.
Checklist antes del 30 de septiembre
- Método de pago en el Billing Hub de cada WABA, o confirmación escrita de tu proveedor si facturas a través de él.
- Parser tolerante:
pricing.typeypricing.categoryse guardan tal cual. Nada de descartar el estado porque traegroup_service,free_group_customer_serviceoauthentication-international. - Deja de decidir con
billable. Usatype === 'regular'más la tarifa decategory. - Tarjeta con fecha de vigencia, cargada con la de octubre (marketing a 0.7298 en México). La próxima fecha en que Meta puede cambiar tarifas es el 1 de enero de 2027.
- Cuenta en
deliveredoread, una vez porwamid, y agrupa porphone_number_idy mes en la zona horaria de tu WABA. - Alertas: quedan menos de 200 de servicio gratis, primer servicio
regulardel mes, categorías sin tarifa y aumento defailed. - Plantillas de utilidad dentro de la ventana: desde el 1 de octubre se cobran desde la primera. Si mandas menos de 1,000 mensajes de servicio al mes, un texto libre dentro de la ventana puede salir gratis donde la plantilla cuesta.
- Plantillas de marketing contra utilidad: Meta te cobra la categoría que tenga la plantilla al momento de usarla, y puede recategorizarla. Suscríbete al webhook
template_category_update, que avisa 24 horas antes de un cambio automático de categoría. - Mensajes por respuesta: junta las burbujas de tu agente. Es la palanca más barata del ejemplo.
- Cruce mensual contra
pricing_analytics, sabiendo que es aproximado.
Si prefieres que alguien revise tu integración, tus plantillas y cuánto te va a costar octubre antes de que llegue, es justo lo que hago en WhatsApp para tu negocio.
Para seguir leyendo:
- Monta un agente de IA en WhatsApp para tu pyme (Laravel + OpenAI): el webhook, la memoria de conversación y las reglas de la ventana de 24 horas, desde cero.
Preguntas frecuentes
¿Qué cambia en la API de WhatsApp el 1 de octubre de 2026?
Meta empieza a cobrar dos tipos de mensaje que hoy son gratis: los mensajes de servicio (los que mandas sin plantilla dentro de la ventana de 24 horas) a partir del mensaje 1,001 de cada mes por número de negocio, y las plantillas de utilidad que mandas dentro de esa ventana, que se cobran desde la primera. En México, ambos a 0.1565 MXN por mensaje entregado. Además, la tarifa de marketing de México sube de 0.5614 a 0.7298 MXN. El cambio entra a las 00:00 en la zona horaria de tu cuenta de WhatsApp Business.
¿Cómo sé en el webhook si un mensaje de WhatsApp se cobró?
Mira pricing.type en el webhook de estados: regular significa que se cobra; free_customer_service, free_entry_point o free_group_customer_service significan que salió gratis. pricing.category te dice qué tarifa se aplicó. No uses pricing.billable para decidir: la referencia de Meta dice que se va a deprecar en una versión futura y recomienda usar type y category juntos.
¿Los 1,000 mensajes gratis de WhatsApp son por cuenta o por número?
Por número de teléfono de negocio. Meta dice que cada número recibe 1,000 mensajes de servicio gratis al mes, que cobra desde el mensaje de servicio 1,001 entregado ese mes y que el cupo no se acumula: se reinicia cada mes. El cupo es solo de mensajes de servicio; las plantillas de utilidad dentro de la ventana no tienen cupo gratis.
¿Qué pasa si no tengo método de pago en WhatsApp Business el 1 de octubre?
Según la página de precios de Meta actualizada el 10 de septiembre de 2026, Meta entrega tus primeros 1,000 mensajes de servicio del mes y deja de entregarlos a partir del 1,001. La página de mensajes sin plantilla pide tener el método de pago en el Billing Hub antes del 30 de septiembre de 2026. Si facturas a través de un proveedor de soluciones, confirma con él que el método de pago está resuelto.
¿El objeto pricing llega en todos los estados del webhook?
No. Según la referencia de Meta, pricing viene con el estado sent y con uno de delivered o read, y en la v24 puede venir solo en delivered. Como Meta cobra lo entregado y a veces manda read sin delivered, cuenta cada mensaje una sola vez por wamid cuando llegue delivered o read, y guarda el pricing venga en el estado que venga.
¿Cuánto va a pagar a Meta una pyme mexicana desde octubre?
Depende del volumen. Con 600 conversaciones al mes de 5 mensajes de servicio cada una, 600 confirmaciones de utilidad dentro de la ventana, 400 recordatorios de utilidad fuera de ella y una campaña de marketing a 500 contactos, la factura de Meta pasa de 343.30 MXN en septiembre a 834.40 MXN en octubre con tarifas de lista. Reducir el agente de 5 a 3 mensajes por conversación baja la parte de servicio de 313.00 a 125.20 MXN.
¿Conviene cambiar plantillas de utilidad por texto libre dentro de la ventana?
Solo si mandas menos de 1,000 mensajes de servicio al mes. En México el servicio y la utilidad cuestan lo mismo (0.1565 MXN) desde el 1 de octubre, pero el servicio tiene 1,000 gratis al mes por número y la utilidad no. Una vez agotado el cupo, da igual. La palanca más grande suele ser que tu agente mande menos mensajes por respuesta.