Webhooks

Manejo de Webhooks

Usa los handlers de webhooks del SDK para Next.js, Hono y Express para recibir y procesar eventos de Whatalo con verificación de firma integrada.

@whatalo/plugin-sdk incluye handlers de webhooks específicos para cada framework que manejan la verificación de firma, el parseo de JSON y el enrutamiento de eventos por ti. Elige el adaptador que corresponda a tu framework de servidor.

Next.js (App Router)

// app/api/webhooks/route.ts
import { createWebhookHandler } from "@whatalo/plugin-sdk/adapters/nextjs";

export const POST = createWebhookHandler({
  secret: process.env.WHATALO_CLIENT_SECRET!,
  handlers: {
    "order.created": async (payload, context) => {
      console.log("New order:", payload.order.id);
      console.log("Delivery:", context.deliveryId);
      // Trigger fulfilment, send confirmation email, etc.
    },
    "order.updated": async (payload) => {
      console.log("Status changed:", payload.order.status);
    },
    "product.updated": async (payload) => {
      console.log("Product changed:", payload.product.id);
      // Sync with your catalogue cache
    },
  },
  onUnhandledEvent: async (event, payload, context) => {
    // Called for any event not listed in handlers above
    console.log(`Unhandled event: ${event}`, context.deliveryId);
  },
});

El handler lee el cuerpo de la solicitud sin procesar antes de parsearlo, lo cual es necesario para la verificación HMAC correcta. No envuelvas esta ruta en ningún middleware de parseo de body.

Hono

import { Hono } from "hono";
import { createWebhookHandler } from "@whatalo/plugin-sdk/adapters/hono";

const app = new Hono();

const handler = createWebhookHandler({
  secret: process.env.WHATALO_CLIENT_SECRET!,
  handlers: {
    "order.created": async (payload) => {
      await processNewOrder(payload.order);
    },
    "customer.created": async (payload) => {
      await syncCustomer(payload.customer);
    },
  },
});

app.post("/api/webhooks", handler);

export default app;

Express

import express from "express";
import { createWebhookHandler } from "@whatalo/plugin-sdk/adapters/express";

const app = express();

const handler = createWebhookHandler({
  secret: process.env.WHATALO_CLIENT_SECRET!,
  handlers: {
    "order.created": async (payload) => {
      await processNewOrder(payload.order);
    },
  },
});

// El adapter verifica la firma sobre el body raw.
// No uses express.json() en esta ruta.
app.post("/api/webhooks", express.raw({ type: "application/json" }), handler);

Si usas express.json() globalmente, monta la ruta raw de webhooks antes del parser global:

app.post("/api/webhooks", express.raw({ type: "application/json" }), handler);
app.use(express.json());

Opciones del Handler

OpciónTipoRequeridoDescripción
secretstringEl secreto de cliente de tu plugin, usado para verificación de firma HMAC
handlersobjectMapa de tipo de evento → función handler async
onUnhandledEventfunctionNoCatch-all llamado para eventos no listados en handlers

Firma del Handler

Cada handler recibe un payload tipado y metadata de entrega tomada de los headers de la solicitud:

type WebhookPayload = {
  event_id?: string;       // Identificador de evento de negocio para idempotencia
  occurred_at?: string;    // Timestamp ISO 8601 del evento
  store: { id: string; name?: string; timezone: string };
  order?: unknown;         // Presente en eventos de pedido
  product?: unknown;       // Presente en eventos de producto
  customer?: unknown;      // Presente en eventos de cliente
};

type WebhookHandlerContext = {
  deliveryId: string; // X-Webhook-Id
  event: string;      // X-Webhook-Event
  timestamp: string;  // X-Webhook-Timestamp
};

El nombre del evento no está en el body. El adaptador lee X-Webhook-Event para seleccionar el handler correspondiente y lo pasa como context.event.

Manejo de Errores en Handlers

Si un handler lanza un error, el adaptador lo captura y devuelve 500 para activar un reintento de Whatalo. Si necesitas detalles diagnósticos, regístralos dentro de tu handler antes de lanzar. Para ignorar silenciosamente un evento sin activar un reintento, retorna normalmente sin lanzar:

"order.created": async (payload) => {
  const order = payload.order as OrderPayload;

  // If the order is already in our system, skip silently
  const exists = await db.orders.findUnique({ where: { id: order.id } });
  if (exists) return; // Returns 200 OK — no retry

  // Process the new order
  await db.orders.create({ data: mapOrder(order) });
},

Verificación Manual (Sin Adaptador de Framework)

Si usas un framework no listado aquí, verifica las firmas manualmente usando verifyWebhook:

import { verifyWebhook } from "@whatalo/plugin-sdk/webhooks";

// rawBody must be the raw string body — read it before JSON.parse
const isValid = verifyWebhook({
  payload: rawBody,
  signature: req.headers["x-webhook-signature"] as string,
  timestamp: req.headers["x-webhook-timestamp"] as string,
  secret: process.env.WHATALO_CLIENT_SECRET!,
});

if (!isValid) {
  return res.status(401).json({ error: "Invalid signature" });
}

const payload = JSON.parse(rawBody);
const event = req.headers["x-webhook-event"];

Consulta Verificación y Seguridad para más detalles sobre el algoritmo de firma y la protección contra replay attacks.

On this page