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) => {
      console.log("New order:", payload.data.id);
      // Trigger fulfilment, send confirmation email, etc.
    },
    "order.status_changed": async (payload) => {
      console.log("Status changed:", payload.data.status);
    },
    "product.updated": async (payload) => {
      console.log("Product changed:", payload.data.id);
      // Sync with your catalogue cache
    },
    "app.installed": async (payload) => {
      // Provision resources for the new merchant
      await provisionMerchant(payload.store);
    },
    "app.uninstalled": async (payload) => {
      // Clean up stored merchant data
      await cleanupMerchant(payload.store);
    },
  },
  onUnhandledEvent: async (event, payload) => {
    // Called for any event not listed in handlers above
    console.log(`Unhandled event: ${event}`);
  },
});

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.data);
    },
    "customer.created": async (payload) => {
      await syncCustomer(payload.data);
    },
  },
});

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

export default app;

Express

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

const app = express();

// Mount BEFORE any global body-parsing middleware on this route
const handler = createWebhookHandler({
  secret: process.env.WHATALO_CLIENT_SECRET!,
  handlers: {
    "order.created": async (payload) => {
      await processNewOrder(payload.data);
    },
  },
});

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

Si usas express.json() globalmente, excluye la ruta de webhooks:

// Apply JSON parsing to all routes except /api/webhooks
app.use((req, res, next) => {
  if (req.path === "/api/webhooks") return next();
  express.json()(req, res, next);
});

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 único argumento tipado payload:

type WebhookPayload = {
  id: string;           // Unique delivery ID (X-Webhook-ID)
  event: string;        // Event type (X-Webhook-Event)
  store: string;        // Store identifier
  timestamp: string;    // ISO 8601 timestamp
  data: unknown;        // Event-specific data (type depends on event)
};

Manejo de Errores en Handlers

Si un handler lanza un error, el adaptador lo captura, registra el stack trace y devuelve 500 para activar un reintento de Whatalo. Para ignorar silenciosamente un evento sin activar un reintento, retorna normalmente sin lanzar:

"order.created": async (payload) => {
  const order = payload.data 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,
  secret: process.env.WHATALO_CLIENT_SECRET!,
});

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

const event = JSON.parse(rawBody);

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

On this page