Webhooks

Recibe notificaciones en tiempo real cuando ocurren eventos en una tienda.

Descripción General

Los webhooks permiten que tu aplicación reciba notificaciones HTTP POST cuando ocurren eventos en una tienda Whatalo. En lugar de hacer polling a la API, tu servidor recibe datos automáticamente.

Antes de empezar: gestionar webhooks requiere una API key con los scopes read:webhooks y write:webhooks. Cuando el comerciante crea la key en Settings > Developer > API Keys, selecciona el grupo de permisos Automatizaciones (que incluye ambos scopes). Una key sin ellos recibe una respuesta 403 SCOPE_INSUFFICIENT. Una vez que la key tiene los scopes, tu integración registra los webhooks por sí misma con POST /v1/webhooks — no hace falta configurarlos manualmente en el panel.

Cómo Funcionan los Webhooks

1. Ocurre un evento (ej. nuevo pedido realizado)
2. Whatalo envía un POST request a tu URL registrada
3. Tu servidor procesa el payload y devuelve 2xx
4. Si la entrega falla, Whatalo reintenta con backoff exponencial

Gestión de Endpoints de Webhooks

Listar Webhooks

GET /v1/webhooks

Scope requerido: read:webhooks

Crear Webhook

POST /v1/webhooks
{
  "url": "https://your-server.com/webhooks/whatalo",
  "events": ["order.created", "order.updated"]
}
CampoTipoRequeridoDescripción
urlstringURL del endpoint HTTPS (debe ser HTTPS)
eventsstring[]Tipos de eventos a suscribir
secretstringNoSecret de firma personalizado (se genera automáticamente si se omite)

Las URLs de webhooks deben usar HTTPS. Los endpoints HTTP son rechazados.

Scope requerido: write:webhooks

Actualizar Webhook

PATCH /v1/webhooks/:id

Scope requerido: write:webhooks

Eliminar Webhook

DELETE /v1/webhooks/:id

Scope requerido: write:webhooks


Tipos de Eventos

EventoDisparador
order.createdNuevo pedido realizado
order.updatedEstado o datos del pedido cambiaron
order.cancelledPedido cancelado
order.completedPedido completado
product.createdNuevo producto creado
product.updatedDatos del producto cambiaron
product.deletedProducto eliminado
customer.createdNuevo cliente registrado
customer.updatedDatos del cliente cambiaron
checkout.completedCheckout completado
checkout.abandonedCheckout marcado como abandonado tras inactividad

Payload del Webhook

Los payloads de webhooks son objetos JSON. El header X-Webhook-Event identifica el tipo de evento y el body contiene el payload específico del evento.

Para eventos de pedido, cliente y producto, event_id es el identificador público de la entidad afectada. Coincide con order.id, customer.id o product.id dentro del mismo payload. Usa X-Webhook-Id para idempotencia por entrega y X-Webhook-Event junto con event_id para deduplicación a nivel de negocio.

order.created

{
  "event_id": "ord_abc123",
  "occurred_at": "2026-03-01T15:00:00.000Z",
  "order": {
    "id": "ord_abc123",
    "order_number": 1042,
    "status": "pending",
    "payment_status": "pending",
    "payment_method": "cash_on_delivery",
    "total": 8997,
    "subtotal": 8997,
    "shipping": 0,
    "discount": 0,
    "coupon_code": null,
    "free_shipping": false,
    "currency": "DOP",
    "created_at": "2026-03-01T15:00:00.000Z",
    "custom_fields": []
  },
  "customer": {
    "id": "cus_abc123",
    "name": "Ana Pérez",
    "email": "[email protected]",
    "phone": "8090000000"
  },
  "store": {
    "id": "sto_abc123",
    "name": "Mi Tienda",
    "timezone": "America/Santo_Domingo"
  }
}

order.updated

Los payloads de actualización de pedidos pueden ser parciales. Para cambios de estado, el body incluye solo los campos modificados:

{
  "event_id": "ord_abc123",
  "occurred_at": "2026-03-01T15:05:00.000Z",
  "order": {
    "id": "ord_abc123",
    "status": "completed"
  },
  "store": {
    "id": "sto_abc123",
    "timezone": "America/Santo_Domingo"
  }
}

Las actualizaciones de pago usan la misma forma parcial con order.payment_status.

Cuando los datos del pedido cambian sin un cambio de estado —por ejemplo, al editar sus líneas de producto— el body omite tanto status como payment_status y solo incluye el id del pedido, occurred_at y la tienda. Trátalo como una señal de "datos modificados" y vuelve a consultar el pedido completo mediante GET /v1/orders/{id}:

{
  "event_id": "ord_abc123",
  "occurred_at": "2026-03-01T15:05:00.000Z",
  "order": {
    "id": "ord_abc123"
  },
  "store": {
    "id": "sto_abc123",
    "timezone": "America/Santo_Domingo"
  }
}

Eventos de Producto

product.created y product.updated usan esta forma:

{
  "event_id": "prd_abc123",
  "occurred_at": "2026-03-01T15:00:00.000Z",
  "product": {
    "id": "prd_abc123",
    "name": "Kit Inicial",
    "slug": "kit-inicial",
    "price": 2500,
    "product_status": "active",
    "archived_at": null,
    "main_image": "https://cdn.example.test/product.jpg"
  },
  "store": {
    "id": "sto_abc123",
    "timezone": "America/Santo_Domingo"
  }
}

product.deleted incluye el identificador del producto eliminado:

{
  "event_id": "prd_abc123",
  "occurred_at": "2026-03-01T15:00:00.000Z",
  "product": {
    "id": "prd_abc123"
  },
  "store": {
    "id": "sto_abc123",
    "timezone": "America/Santo_Domingo"
  }
}

Eventos de Cliente

customer.created y customer.updated usan esta forma:

{
  "event_id": "cus_abc123",
  "occurred_at": "2026-03-01T15:00:00.000Z",
  "customer": {
    "id": "cus_abc123",
    "name": "Ana Pérez",
    "email": "[email protected]",
    "phone": "8090000000"
  },
  "store": {
    "id": "sto_abc123",
    "name": "Mi Tienda",
    "timezone": "America/Santo_Domingo"
  }
}

checkout.abandoned

Se dispara cuando un borrador de checkout se marca como abandonado tras un periodo de inactividad (un trabajo en segundo plano evalúa los borradores periódicamente). Usa checkout.recovery_url para traer de vuelta al comprador a su carrito.

Función premium: este evento requiere que el plan de la tienda incluya Carritos Abandonados. Suscribirse sin ella devuelve 403 FEATURE_ACCESS_DENIED, y las entregas se pausan automáticamente mientras la tienda esté en un plan sin la función (el plan se verifica en cada disparo). Los checkouts abandonados también pueden leerse bajo demanda vía GET /v1/checkout-drafts (scope read:checkout_drafts, mismo requisito de plan).

El event_id es el identificador público del checkout abandonado y coincide con checkout.id. A diferencia de los eventos de pedido, customer no tiene id — un borrador abandonado se captura antes de que exista un registro de cliente, por lo que solo lleva los campos de contacto (nulos permitidos). Siempre está presente email o phone.

{
  "event_id": "chk_abc123",
  "occurred_at": "2026-03-01T15:30:00.000Z",
  "checkout": {
    "id": "chk_abc123",
    "status": "abandoned",
    "total": 8997,
    "subtotal": 8997,
    "shipping": 0,
    "discount": 0,
    "currency": "DOP",
    "recovery_url": "https://mitienda.example/r/RECOVERYTOKEN",
    "created_at": "2026-03-01T15:00:00.000Z",
    "abandoned_at": "2026-03-01T15:30:00.000Z",
    "shipping_address": {
      "name": "Ana Perez",
      "phone": "8090000000",
      "address": "Calle 1 #2",
      "city": "Santo Domingo",
      "province": "Distrito Nacional",
      "country": "República Dominicana",
      "postal_code": "10101",
      "notes": null
    },
    "items": [
      {
        "product_id": "prd_abc123",
        "product_name": "Starter Kit",
        "variant_name": "Color: Rojo",
        "quantity": 2,
        "unit_price": 4498.5,
        "total_price": 8997
      }
    ]
  },
  "customer": {
    "name": "Ana Perez",
    "email": "[email protected]",
    "phone": "8090000000"
  },
  "store": {
    "id": "sto_abc123",
    "name": "Mi Tienda",
    "timezone": "America/Santo_Domingo"
  }
}

Cuando el borrador abandonado capturó order bumps, el webhook también incluye checkout.order_bumps con { total, items: [{ bump_id, title, price }] }. La clave se omite cuando no hubo ninguno.

Los campos monetarios como total, subtotal, shipping, discount y price usan unidades mayores decimales, no unidades menores ni centavos. Ejemplo: 1500.50 significa 1500.50 en la moneda del payload.


Seguridad de Webhooks

Cada entrega de webhook incluye estos headers:

HeaderDescripción
X-Webhook-IdID único de entrega. Guárdalo para idempotencia.
X-Webhook-TimestampTimestamp Unix en segundos.
X-Webhook-SignatureDigest hexadecimal HMAC-SHA256.
X-Webhook-EventNombre del evento, por ejemplo order.created.

La firma se calcula así:

HMAC-SHA256(secret, `${timestamp}.${rawBody}`) -> hex

Usa el body raw exactamente como fue recibido, antes de hacer JSON.parse. Rechaza timestamps con más de 300 segundos de antigüedad para reducir el riesgo de replay.

Para idempotencia, guarda X-Webhook-Id y omite entregas duplicadas. Para deduplicación de negocio, también puedes combinar X-Webhook-Event con el event_id del body.

Ejemplo de Verificación

Node.js
import crypto from "crypto";

function verifyWebhookSignature(rawBody, headers, secret) {
  const timestamp = headers["x-webhook-timestamp"];
  const signature = headers["x-webhook-signature"];

  if (!timestamp || !signature || !/^[a-f0-9]{64}$/i.test(signature)) return false;

  const timestampValue = Number(timestamp);
  if (!Number.isInteger(timestampValue)) return false;

  const ageSeconds = Math.abs(Math.floor(Date.now() / 1000) - timestampValue);
  if (ageSeconds > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`, "utf8")
    .digest("hex");

  try {
    return crypto.timingSafeEqual(
      Buffer.from(signature, "hex"),
      Buffer.from(expected, "hex")
    );
  } catch {
    return false;
  }
}

Política de Reintentos

Si tu endpoint devuelve un código de estado diferente a 2xx, Whatalo reintenta con backoff exponencial:

IntentoDemora antes del intento
2do intento1 segundo
3er intento2 segundos

Las respuestas 4xx se tratan como errores del cliente y no se reintentan. Las respuestas 5xx y los errores de red se reintentan hasta 3 intentos totales.

On this page