Cambios de API

Changelogs de webhooks: el breaking change que nadie pidió

6 min de lectura

Un changelog de API REST existe porque un consumidor puede elegir rechazar una respuesta que no entiende, o al menos registrar un error lo bastante ruidoso para que alguien lo note. Un receptor de webhook rara vez hace ninguna de las dos cosas. Recibe un POST, lee los campos que espera, y si un campo se movió, cambió de tipo o desapareció, el endpoint o bien falla en silencio dentro de un job en segundo plano que nadie vigila o, peor, sigue funcionando con un valor incorrecto que nunca validó. Qué es un breaking change cubre la definición general; un payload de webhook necesita su propia respuesta, porque el modo de fallo es distinto al de un endpoint que alguien llama a propósito.

¿Por qué un cambio de payload de webhook rompe distinto que un cambio en la respuesta de una API?

Porque la dirección de la petición está invertida. Un consumidor REST inicia la llamada y puede añadir una cabecera de versión, reintentar ante un 4xx o leer un aviso de deprecación en la respuesta. Un receptor de webhook no inició nada de eso: tu servidor decidió enviar, decidió cuándo, y decidió qué forma tendría el cuerpo. La única palanca del receptor es la validación que escribió cuando se construyó la integración, y la mayoría de integraciones se construyen una vez, funcionan, y nadie las revisa de nuevo hasta que se rompen. Esa asimetría es todo el motivo por el que un cambio de payload de webhook merece más cautela que el mismo cambio en un cuerpo de respuesta que un consumidor pidió activamente.

¿Qué cuenta realmente como breaking change en un payload de webhook?

CambioRompedor para la mayoría de receptores
Añadir un campo nuevoNo, si los receptores ignoran campos desconocidos (verifica esta suposición, no la des por hecha)
Eliminar un campoSí, si algo lo lee
Renombrar un campoSí, funcionalmente idéntico a eliminar el antiguo
Cambiar el tipo de un campo (string a objeto)Sí, casi siempre
Reordenar campos en el cuerpo JSONNo, para cualquier receptor que parsee por clave, que deberían ser todos
Cambiar el nombre o tipo de eventoSí, si los receptores filtran o enrutan por él

La fila de “añadir un campo es seguro” es la que más se apoyan los equipos y la que más vale la pena verificar en vez de asumir. Un parser JSON permisivo ignora campos desconocidos por defecto, pero un receptor que deserializa en un esquema estricto, varios lenguajes tipados lo hacen sin configuración extra, puede rechazar todo el payload en cuanto aparece un campo inesperado. Añadir un campo es seguro para tu webhook solo si sabes cómo parsean los receptores, no porque JSON en sí sea permisivo.

¿Cómo se versiona un payload de webhook?

Más o menos como para una respuesta de API, con un matiz: el receptor nunca envía una petición, así que no puede pedir una versión, y el emisor tiene que indicarla. Puede ir en el cuerpo o en una cabecera de la petición de la propia entrega; las entregas de GitHub llevan X-GitHub-Event y X-GitHub-Hook-ID, y la especificación Standard Webhooks pone sus metadatos en cabeceras webhook-*. Un campo de versión en el payload ("payload_version": 2) es la opción más barata y funciona cuando los receptores están dispuestos a bifurcar según él. Un tipo de evento versionado (invoice.updated se convierte en invoice.updated.v2 como un evento distinto al que un receptor se suscribe voluntariamente) cuesta más de construir pero significa que la forma antigua sigue llegando a quien nunca migró, lo cual importa más aquí que en un endpoint REST porque no puedes llamar a cada receptor para pedirle que actualice. Un ajuste por suscripción, elegido al registrar el endpoint del webhook, adelanta la decisión en vez de bifurcar en cada entrega, y es la opción correcta cuando ya tienes un registro de suscripción al que engancharlo.

POST /endpoint-del-receptor
{
  "event": "invoice.updated",
  "payload_version": 2,
  "data": { "invoice_id": "inv_123", "status": "paid" }
}

¿Cómo sabes siquiera quién está escuchando?

Peor que la versión equivalente de este problema en un changelog de API, porque un webhook no tiene un registro de peticiones entrantes en tu lado que nombre al consumidor; solo tienes tu propio registro de entregas salientes, que te dice que un endpoint recibió un 200, no qué hizo con el cuerpo. Rastrea al menos dos cosas: cada endpoint registrado con una dueña, la misma disciplina que los changelogs de API interna recomiendan para consumidores internos, y tu tasa de fallos de entrega por endpoint tras un cambio de payload. Un pico de respuestas 4xx o 5xx desde un endpoint justo después de un cambio es lo más cercano a un stack trace que vas a obtener, y a menudo es la única señal de que un receptor se rompió, porque el equipo que lo opera puede tardar días en notarlo.

¿Debería un changelog de webhooks estar separado del changelog de API?

Una sección aparte en la misma página, no una publicación distinta. Un changelog de API ya establece quién lo lee y cómo se suscribe; un cambio de payload de webhook pertenece al mismo feed, etiquetado con la claridad suficiente para que una desarrolladora del lado receptor que busca “esto afecta a mi integración” pueda filtrarlo, porque una consumidora de webhooks a menudo no tiene otro motivo para revisar un changelog general de API y solo lo encontrará si alguien la enlaza directamente ahí.

¿Cómo debería ser una ventana de deprecación razonable para un payload de webhook?

Más larga que la deprecación REST equivalente, porque migrar del lado receptor normalmente significa que otro equipo, uno con el que quizá no tengas línea directa, tiene que notarlo, planificarlo y lanzarlo sin urgencia propia. Un mes es un mínimo razonable para un campo que el receptor plausiblemente sigue parseando con una librería permisiva; tres meses o más es más seguro para eliminar un campo que un esquema estricto rechazaría por completo. Envía la forma antigua y la nueva juntas durante la ventana cuando sea posible (el campo antiguo status y su reemplazo de la versión 2 en el mismo payload), porque un receptor que lee el campo antiguo sigue funcionando sin tocar su código, y uno que ya migró simplemente ignora el campo que ya no necesita.

FAQ

¿Los consumidores de webhooks necesitan confirmar un cambio de payload antes de que se publique? No existe un mecanismo de confirmación por defecto, y por eso la ventana de deprecación importa más aquí que en una API REST: nadie confirma estar listo, así que la ventana tiene que ser lo bastante larga para que la mayoría de receptores migre por su cuenta antes de que la forma antigua desaparezca.

¿Alguna vez es seguro añadir campos desconocidos sin avisar? Solo una vez que has verificado, no asumido, que tus receptores parsean de forma permisiva. Una entrada de changelog cuesta poco y elimina la incertidumbre; añadir campos en silencio asumiendo que “los parsers JSON ignoran los extras” rompe a cualquier receptor con deserialización estricta.

¿Cuál es la forma más rápida de detectar un receptor de webhook roto tras un cambio de payload? Una tasa de fallos de entrega por endpoint, observada en las horas justo después del cambio. No te dirá qué se rompió, solo que algo se rompió, pero es la señal más temprana y a menudo la única que obtendrás.

¿La lógica de reintentos ayuda a los receptores a sobrevivir a un cambio de payload? No. Un reintento reenvía el mismo payload nuevo; no vuelve a una forma que el receptor pueda parsear. Un cambio de payload rompe a un receptor en la primera entrega y en cada reintento siguiente de la misma forma.


Las afirmaciones técnicas de este artículo no se han revisado de forma independiente. Si algo está mal, avísanos y lo corregiremos.

Relacionado en changeloop: Documentación para desarrolladores, Herramientas de changelog comparadas

changeloop
El equipo que crea un changelog que cierra el círculo. Tus usuarios lo piden, tu equipo lo publica y quien lo pidió se entera.