¡Qué onda, devs! Imagínate esta pesadilla: montas un servicio en Node.js para recibir pagos a través de un webhook. Durante semanas, la aplicación parece responder genial. Los logs muestran que los eventos llegan a tiempo, las respuestas devuelven un código 200 OK y el ID del pedido impreso en pantalla se ve idéntico al del cliente. Sin embargo, un día te habla el área de soporte: hay clientes furiosos porque su suscripción no se activó, mientras que a otros les regalaste membresías sin pagar.

Revisas la base de datos y te das cuenta de lo peor: el webhook estuvo actualizando la fila equivocada en tu base de datos todo este tiempo. ¿Cómo es posible si los logs decían que el ID era correcto?

En este artículo aprenderemos qué provoca este fallo clásico en JavaScript, por qué los enteros de 64 bits son engañosos al parsear JSON y cómo solucionarlo en tus proyectos web.


El culpable: La precisión numérica en JavaScript

En la mayoría de los lenguajes de programación (como Java, C# o PHP), existen distintos tipos de datos para números: int, long, float, double, etc. Sin embargo, en JavaScript tradicional solo existía un tipo numérico: Number.

El tipo Number en JavaScript utiliza el formato de coma flotante de doble precisión IEEE 754 de 64 bits. Esto significa que JavaScript solo puede representar enteros de forma exacta hasta cierto límite. Ese límite se conoce como el entero seguro máximo (MAX_SAFE_INTEGER).

Podemos consultar este valor directamente en la consola de Node.js o del navegador:

console.log(Number.MAX_SAFE_INTEGER);
// Resultado: 9007199254740991 (2^53 - 1)

¿Qué pasa si superas ese límite?

Si una base de datos (como MySQL o PostgreSQL) genera un identificador BIGINT de 64 bits (por ejemplo: 9007199254740993) y tu API lo recibe en un payload JSON como un número, JavaScript perderá precisión al convertirlo con JSON.parse().

Ejemplo directo:

const jsonRecibido = '{"orderId": 9007199254740993}';
const datos = JSON.parse(jsonRecibido);

console.log(datos.orderId);
// Imprime: 9007199254740992  <-- ¡Se redondeó al entero par más cercano!

¡El ID cambió por completo! Al ejecutar la consulta en la base de datos (UPDATE orders SET paid = true WHERE id = 9007199254740992), terminarás modificando el registro de otro cliente.


Demostración paso a paso del error

Veamos un caso real paso a paso para entender cómo se produce la falla al procesar un webhook.

Paso 1: El servidor remoto envía el Webhook

La pasarela de pagos envía la petición con la siguiente información en formato JSON puro:

{
  "event": "payment.success",
  "transaction_id": 9876543210123456789,
  "status": "completed"
}

Paso 2: Tu servidor procesa la petición en Node.js

// Simulación de recepción de payload HTTP
const rawBody = '{"event": "payment.success", "transaction_id": 9876543210123456789, "status": "completed"}';

// Al hacer parse tradicional:
const payload = JSON.parse(rawBody);

console.log("ID procesado:", payload.transaction_id);
// Muestra: 9876543210123456000 (¡perdió los últimos dígitos!)

Paso 3: Ejecución de la consulta SQL

Cuando intentas actualizar tu base de datos MySQL:

const query = `UPDATE transactions SET status = '${payload.status}' WHERE id = ${payload.transaction_id}`;
console.log(query);
// Query generada: UPDATE transactions SET status = 'completed' WHERE id = 9876543210123456000

Como el ID real en la base de datos era 9876543210123456789, la consulta SQL actualizará otra fila distinta o no afectará a ninguna, rompiendo la lógica de tu negocio.


¿Cómo solucionarlo correctamente?

Existen dos enfoques principales para evitar la pérdida de precisión al trabajar con IDs en JavaScript.

Solución 1: Exigir o enviar IDs como cadenas de texto (Strings)

Esta es la mejor práctica en el diseño de APIs REST y Webhooks. Los identificadores únicos nunca deben operarse matemáticamente (no vas a sumar ni restar IDs), por lo que siempre deben transmitirse como strings.

JSON correcto:

{
  "event": "payment.success",
  "transaction_id": "9876543210123456789",
  "status": "completed"
}

Al ser una cadena de texto, JSON.parse() mantendrá intacto cada carácter sin perder precisión.

Solución 2: Usar bibliotecas para parsear números grandes a BigInt

Si no tienes control sobre el servicio que te envía el JSON y este manda los IDs como números sin comillas, debes procesar el texto plano antes de que el parseador nativo los arruine.

Puedes usar paquetes como json-bigint en Node.js:

// Ejemplo usando la librería 'json-bigint'
const JSONbig = require('json-bigint')({ storeAsString: true });

const rawBody = '{"event": "payment.success", "transaction_id": 9876543210123456789}';

// Mantiene los números grandes como cadenas de texto
const payload = JSONbig.parse(rawBody);

console.log(payload.transaction_id);
// Resultado: "9876543210123456789" (¡Sin redondeos!)

Errores comunes al manejar IDs y Webhooks

  1. Confiar a ciegas en JSON.parse() nativo: Dar por hecho que el parser por defecto de Express/Node.js convertirá todo a salvo.
  2. Usar tipos INT o BIGINT numéricos en el cliente: Convertir las respuestas de la BD a Number antes de enviarlas al frontend en aplicaciones Fullstack.
  3. No imprimir el cuerpo sin procesar (raw body): Cuando depures un error de webhooks, imprime la cadena limpia recibida directamente del socket para verificar si venía como número o como texto.

Buenas prácticas para tu desarrollo

  • Trata los IDs como cadenas de texto: En tus modelos de datos y esquemas de validación (como Zod, Joi o Yup), define los IDs siempre como tipos de cadena (string).
  • Usa UUIDs o ULIDs: Si diseñas tu propia base de datos, considera usar identificadores únicos universales en lugar de autoincrementables numéricos. Esto evita colisiones y problemas de límite numérico.
  • Configura parsers personalizados en Express: Si tu backend recibe payloads con enteros de 64 bits, configura parsers como json-bigint en los middlewares de entrada.

Ejercicio práctico

Escribe un pequeño script en Node.js o en la consola del navegador que haga lo siguiente:

  1. Define una variable const idString = "9007199254740995";.
  2. Conviértela a número usando Number(idString) e imprime el resultado.
  3. Ahora usa la característica nativa de JavaScript moderno BigInt(idString) e imprímela.
  4. Compara ambos resultados y observa en qué punto se destruye la precisión.

Fuente original

Este artículo está inspirado en la experiencia real compartida en el post: The Webhook Updated the Wrong Row. The Logs Showed the Perfect ID.