Analizar

Entregas firmadas y un correo que no dice nada.

Un webhook envía cada nueva respuesta a una URL https que usted elija. Cada entrega lleva una cabecera X-NumoForms-Signature: sha256= seguido de un HMAC-SHA256 del cuerpo exacto de la petición, calculado con un secreto único de ese webhook, de modo que su extremo puede demostrar que la carga viene de nosotros. Por separado, las direcciones de correo que elija pueden recibir un aviso cuando llega una respuesta, y esos correos no contienen ninguna respuesta, a propósito.

Qué llega a su extremo

Un único POST en JSON por respuesta. El cuerpo contiene cinco campos y nada más, así que puede escribir un analizador sin adivinar nada.

CampoQué contiene
eventresponse.created
survey_idLa encuesta a la que pertenece la respuesta.
response_idEl identificador único de esta respuesta.
created_atCuándo se registró la respuesta.
answersLas respuestas, indexadas por identificador de pregunta.
CabeceraPara qué sirve
X-NumoForms-EventEl nombre del evento. Hoy solo hay uno: response.created.
X-NumoForms-Signaturesha256= seguido del HMAC-SHA256 del cuerpo exacto de la petición, en hexadecimal.
X-NumoForms-DeliveryUn identificador único de este intento de entrega, para que pueda detectar un duplicado.

La entrega la dispara un disparador de base de datos sobre la tabla de respuestas, no una pantalla concreta de la aplicación. Eso importa más de lo que parece: una respuesta enviada desde un formulario insertado, desde un enlace alojado o desde una parcial retomada días después con guardar y continuar siguen todas el mismo camino, así que no hay ninguna vía que se nos olvidara conectar.

El webhook se dispara cuando la respuesta llega. Si ha activado las aprobaciones, no espera a la decisión de aprobar o rechazar, y no se envía una segunda entrega cuando se toma esa decisión. Interprete una carga entrante como «existe una respuesta», no como «una respuesta ha sido aprobada».

Por qué la firma no es opcional

La URL de un webhook es un secreto que se filtra. Acaba en una incidencia, en un mensaje de Slack, en una captura dentro de un documento de traspaso, en el historial de un portátil compartido. Sin firma, cualquiera que haya visto la URL puede enviarle lo que quiera, y sus sistemas lo archivarán como una respuesta auténtica a una consulta. Para un ayuntamiento que cuenta alegaciones a un expediente urbanístico, eso no es un problema teórico.

Por eso cada entrega va firmada. Cada webhook recibe su propio secreto generado al azar, visible en el editor tras un control de «mostrar secreto de firma». Nosotros calculamos un HMAC-SHA256 sobre los bytes exactos del cuerpo de la petición con ese secreto; usted lo recalcula con su copia y compara. Una petición falsificada no pasa, porque quien la falsifica no tiene el secreto.

La URL debe ser https. Lo impone una restricción de base de datos, no una indicación en el formulario, porque un secreto de firma que viaja por una conexión sin cifrar no es un secreto.

Verificar una entrega en Node.js

Dos detalles causan casi todos los problemas en la práctica. Use el cuerpo en bruto de la petición, no un objeto analizado y vuelto a serializar: las diferencias de espacios en blanco cambian el hash. Y compare en tiempo constante, para que nadie pueda deducir la firma correcta byte a byte a partir de los tiempos de respuesta.

import { createHmac, timingSafeEqual } from 'node:crypto';

const SECRET = process.env.NOISSIME_WEBHOOK_SECRET;

// rawBody must be the exact bytes received, before any JSON parsing.
// Re-serialising a parsed object changes the whitespace and the check fails.
function isFromNoissime(rawBody, header) {
  if (typeof header !== 'string') return false;

  const expected =
    'sha256=' + createHmac('sha256', SECRET).update(rawBody).digest('hex');

  const a = Buffer.from(expected);
  const b = Buffer.from(header);

  // timingSafeEqual throws on a length mismatch, so compare lengths first.
  return a.length === b.length && timingSafeEqual(a, b);
}

Llame a eso antes de analizar nada. Si devuelve falso, responda 401 y descarte la petición. Si devuelve verdadero, tiene una respuesta que solo puede proceder de su encuesta.

El registro de entregas

Un webhook que ha dejado de funcionar en silencio tiene exactamente el mismo aspecto que uno al que nunca se llamó: no pasa nada y nada se lo dice. Por eso cada intento se anota en un registro de entregas, y el último código HTTP se muestra junto al webhook en el editor: verde para un 2xx, rojo para cualquier otra cosa.

La entrega es asíncrona, así que el estado se concilia después y no se supone en el momento del envío. Lo que ve es el código que su servidor devolvió realmente. Cada webhook puede pausarse y reanudarse sin borrarlo, que es lo habitual mientras se redespliega el sistema receptor.

Un límite deliberado: no hay reintento con espera exponencial. La entrega se intenta una vez, se anota el resultado y ahí acaba. Construir una cola de reintentos que respete el orden y la idempotencia es un trabajo serio, y afirmar que tenemos una que no hemos construido sería peor que decir esto. Mientras tanto la respuesta nunca se pierde: está en la base de datos, visible en analítica e informes, y se puede exportar en CSV.

Un webhook que falla tampoco puede romper la recogida. Si la entrega lanza un error, se registra y la respuesta se guarda igualmente. Lo que importa es la respuesta de la persona; la entrega es un intento de mejor esfuerzo.

Avisos por correo, sin las respuestas

Añada las direcciones que deban enterarse de las nuevas respuestas y recibirán un correo cuando llegue una. El mensaje es HTML con la marca y con una alternativa en texto plano, porque tanto los clientes de solo texto como los filtros antispam leen la parte de texto. Nombra la encuesta, da el total acumulado de respuestas y enlaza a los resultados.

Lo que no contiene son las respuestas. Es una decisión, no un olvido. Un correo de aviso se reenvía a un compañero, se archiva automáticamente en un buzón compartido, se sincroniza con un móvil y se conserva en una copia de seguridad durante años. Meter en esa cadena respuestas de texto libre de una consulta dispersa datos personales por sistemas que nadie ha evaluado y deshace discretamente los controles de acceso que se describen en la página de seguridad. Los datos de quien responde se quedan en la base de datos, y el correo le dice que vaya a mirarlos.

Igual que los webhooks, los avisos los dispara un disparador de base de datos, así que siguen a la respuesta y no a una vía de envío concreta. Si no hay ninguna dirección configurada, no se envía nada ni se intenta nada.

Qué no hace esto

Preguntas habituales

¿Cómo verifico la cabecera X-NumoForms-Signature?

Calcule un HMAC-SHA256 del cuerpo en bruto de la petición con el secreto de firma de ese webhook, codifíquelo en hexadecimal, antepóngale «sha256=» y compárelo con la cabecera mediante una comparación de tiempo constante. Verifique antes de analizar el JSON y rechace con un 401 cualquier cosa que no coincida.

¿Qué ocurre si mi extremo está caído cuando llega una respuesta?

La entrega se intenta una vez y el resultado se anota en el registro de entregas con el código HTTP. No hay reintento automático con espera progresiva. La respuesta en sí nunca corre peligro: un fallo de webhook no puede impedir que se guarde una respuesta, y siempre puede recuperar los datos desde la pantalla de resultados o con una exportación a CSV.

¿Se dispara un webhook con una respuesta enviada desde un formulario insertado?

Sí. La entrega la dispara un disparador de base de datos sobre la tabla de respuestas y no una pantalla concreta de la aplicación, así que ocurre venga de donde venga la respuesta: un enlace alojado, un formulario insertado o una parcial retomada que se acaba de completar.

¿Puedo enviar un webhook a una URL http://?

No. Una restricción de comprobación en la base de datos exige que la URL empiece por https:// y el editor rechaza cualquier otra cosa. Un secreto de firma enviado por una conexión sin cifrar no es un secreto.

¿Contienen los correos de aviso las respuestas de la persona encuestada?

No, deliberadamente. El correo indica que ha llegado una respuesta, nombra la encuesta, da el total acumulado y enlaza a los resultados. Los avisos se reenvían y quedan en las copias de seguridad de los buzones, así que los datos de quienes responden se quedan en la base de datos, que es donde están los controles de acceso.

¿Hay una aplicación de Zapier o una integración con Google Sheets?

No. Los webhooks son hoy la única integración saliente. No hay aplicación de Zapier ni conector de Google Sheets, y ninguno de los dos tiene fecha publicada.

Conecte una encuesta con sus propios sistemas.

Apunte un webhook a un endpoint https y cada respuesta llegará firmada, de modo que quien la reciba pueda demostrar que viene de nosotros y no de cualquiera que haya dado con la URL. Añada las direcciones a las que avisar cuando entre una respuesta y las contestaciones se quedan en la base de datos, donde están los controles de acceso.

  • Firma HMAC-SHA256 en cada entrega
  • Registro de entregas con el último estado HTTP
  • Lo dispara la base de datos, venga la respuesta por donde venga
Empezar a crear