Skip to main content
Los webhooks te permiten recibir notificaciones HTTP en tu propio servidor cada vez que ocurre un evento relevante en woku: una nueva reseña woku, un NPS, un CSAT, un CES, la respuesta de un formulario o un ticket de soporte generado por IA. woku hace un POST con un cuerpo JSON a la URL que configures.

Configuración

Los webhooks se administran desde la aplicación administrativa, no por API pública.
1

Abre la configuración de integraciones

En el panel, entra a Empresa → Integraciones → Webhooks.
2

Crea un webhook

Pulsa Nuevo webhook e ingresa la URL de tu endpoint, los eventos a los que te suscribes y, opcionalmente, headers personalizados y la política de reintentos.
3

Guarda el secret

Al crear el webhook, woku muestra el secret una sola vez. Cópialo y guárdalo de forma segura: lo necesitarás para verificar la firma de cada evento.
El secret se muestra una única vez al crear o rotar el webhook. woku lo guarda cifrado y no puede volver a mostrártelo. Si lo pierdes, rótalo desde el panel para generar uno nuevo.

Eventos disponibles

woku emite estos eventos:
EventoCuándo se emite
qualification.createdSe registró una nueva reseña woku (calificación 1-5).
nps_submission.createdSe registró una nueva respuesta NPS (puntaje 0-10).
csat_submission.createdSe registró una nueva respuesta CSAT (1-5).
ces_submission.createdSe registró una nueva respuesta CES (1-5).
form_submission.createdSe completó una respuesta de formulario.
ticket.createdLa IA de woku creó un ticket de soporte desde una opinión negativa (ver Tickets generados por IA).

Estructura común

Todos los payloads comparten esta envoltura:
CampoDescripción
eventNombre del evento (ver tabla anterior).
timestampFecha y hora de emisión (ISO 8601, UTC).
companyIdEmpresa a la que pertenece el evento.
dataDatos específicos del evento.

Ejemplos de payload

Los payloads no incluyen datos de contacto del cliente, con una excepción: ticket.created, cuyo caso incluye la identidad del cliente para que el equipo de soporte pueda contactarlo.
{
  "event": "qualification.created",
  "timestamp": "2026-05-26T15:30:00Z",
  "companyId": "64a1b2c3d4e5f6a7b8c9d0e1",
  "data": {
    "clientId": "64f1a2b3c4d5e6f7a8b9c0d1",
    "wokuId": "64d4e5f6a7b8c9d0e1f2a3b4",
    "qualification": 5,
    "responseChannel": "review-app"
  }
}
{
  "event": "nps_submission.created",
  "timestamp": "2026-05-26T15:30:00Z",
  "companyId": "64a1b2c3d4e5f6a7b8c9d0e1",
  "data": {
    "npsId": "64b2c3d4e5f6a7b8c9d0e1f2",
    "npsToolId": "64c3d4e5f6a7b8c9d0e1f2a3",
    "responseChannel": "whatsapp"
  }
}
{
  "event": "csat_submission.created",
  "timestamp": "2026-05-26T15:30:00Z",
  "companyId": "64a1b2c3d4e5f6a7b8c9d0e1",
  "data": {
    "csatId": "64b2c3d4e5f6a7b8c9d0e1f2",
    "csatToolId": "64c3d4e5f6a7b8c9d0e1f2a3",
    "responseChannel": "review-app"
  }
}
{
  "event": "form_submission.created",
  "timestamp": "2026-05-26T15:30:00Z",
  "companyId": "64a1b2c3d4e5f6a7b8c9d0e1",
  "data": {
    "formResponseId": "64b2c3d4e5f6a7b8c9d0e1f2",
    "formId": "64c3d4e5f6a7b8c9d0e1f2a3",
    "responseChannel": "api"
  }
}
ces_submission.created es el espejo de csat_submission.created con cesId y cesToolId.

Tickets generados por IA (ticket.created)

Cuando la IA de woku detecta riesgo de perder a un cliente por una mala experiencia, crea un ticket de soporte. Puedes recibirlo de dos formas, y usar ambas a la vez:
  • Conectores directos (recomendado, sin código): en Soporte → Configurar conectas woku a tu plataforma de soporte (Zendesk, Salesforce, Slack o un endpoint personalizado). woku crea el ticket directamente en la plataforma; el ruteo por trackers y el formato de cada plataforma se resuelven en el servidor de woku, sin que expongas ningún servicio propio.
  • Webhook (para integraciones a medida): suscribe un webhook al evento ticket.created y recibe el caso completo en formato canónico, listo para crearlo en tu plataforma con tu propio código.
A diferencia del resto de los eventos, el payload de ticket.created lleva la identidad del cliente para que soporte pueda contactarlo.

Conectores directos (sin código)

En Soporte → Configurar agregas uno o más destinos. Cada destino representa una unidad de tu SAC (por ejemplo “SAC Chile” y “SAC México”): apunta a una plataforma (zendesk, salesforce, slack o personalizado), guarda sus credenciales de forma cifrada e identifica el equipo que recibe el ticket dentro de la plataforma, nunca un agente suelto:
  • Zendesk: el grupo de agentes (group_id).
  • Salesforce: la cola de casos (OwnerId, un id 00G...).
  • Slack: el canal (queda definido por la URL del webhook entrante).
Cada destino define además sus condiciones de ruteo sobre los valores de los trackers: el ticket llega al destino cuando se cumplen todas sus condiciones (y cada condición se cumple con cualquiera de sus valores). Por ejemplo, “SAC Chile” recibe los tickets con País = Chile. Si un ticket no cumple las condiciones de ningún destino, va al destino por defecto. El formato del cuerpo lo resuelve woku según la plataforma; para el destino personalizado defines tú el template con placeholders sobre el payload canónico (por ejemplo {{case.subject}}).

Webhook con payload canónico

Un webhook suscrito a ticket.created recibe siempre el payload canónico completo (los webhooks no transforman el cuerpo ni participan del ruteo: todo webhook suscrito recibe todos los tickets). El campo routing.matched indica si el ticket se ruteó a un destino directo (route), al destino por defecto (default) o a ninguno (null):
ticket.created (canonical)
{
  "event": "ticket.created",
  "timestamp": "2026-07-01T14:32:11Z",
  "companyId": "64a1b2c3d4e5f6a7b8c9d0e1",
  "schemaVersion": "2026-07-01",
  "data": {
    "case": {
      "externalId": "woku_ticket_665f0a...",
      "subject": "Atraso reiterado en entregas",
      "description": { "text": "…", "html": "<p>…</p>" },
      "priority": "urgent",
      "type": "problem",
      "tags": ["woku", "voc", "csat", "churn-high"],
      "requester": { "name": "María Gómez", "email": "maria@cliente.com", "phone": "+56 9 ..." },
      "source": {
        "tool": "csat",
        "instrumentName": "CSAT post entrega",
        "trackers": [{ "name": "Locación", "value": "Sucursal Centro" }],
        "responseId": "64b2c3d4...",
        "score": 1,
        "scaleMax": 5,
        "comment": "Tercera vez que el pedido llega tarde…",
        "language": "es",
        "respondedAt": "2026-07-01T14:20:03Z"
      },
      "routing": { "matched": "route" },
      "assessment": {
        "generatedByAI": true,
        "churnRisk": "high",
        "criteria": ["B1_churn_intent", "B3_repeat_issue"],
        "rationale": "Cliente con entregas tardías repetidas amenaza con irse",
        "whatCustomerWants": "Que el pedido llegue a tiempo",
        "suggestedResolution": "Llamar hoy y compensar el envío",
        "historySummary": "8 respuestas del cliente (csat: 5, nps: 3) 3 negativas desde 2026-01-15",
        "model": "gpt-5.5"
      },
      "links": { "ticketUrl": "https://admin.woku.app/companies/.../tickets?ticketId=..." }
    }
  }
}
Campos clave del caso:
CampoDescripción
externalIdIdentificador estable del ticket en woku; úsalo para deduplicar reintentos.
subjectTítulo del problema (máximo 255 caracteres).
descriptionNarrativa completa del caso en text (plano) y html.
priorityurgent, high, normal o low.
requesterIdentidad del cliente afectado (nombre, email, teléfono).
sourceInstrumento de origen, trackers, score, comentario textual y fecha de la respuesta.
routingResultado del ruteo a un destino directo (matched: route, default o null). Los trackers del caso van en source.trackers.
assessmentPor qué la IA escaló: riesgo de fuga, criterios de la rúbrica, qué pide el cliente y la acción sugerida.
links.ticketUrlEnlace al detalle del ticket en woku.

Verificación de firma (HMAC)

Cada entrega incluye el header X-Woku-Signature con una firma HMAC-SHA256 del cuerpo JSON crudo, usando tu secret como llave:
X-Woku-Signature: sha256=<hex>
Calcula la firma sobre el cuerpo crudo de la solicitud (los bytes exactos recibidos), antes de parsearlo como JSON. Re-serializar el objeto puede cambiar el orden de las llaves o el espaciado y producir una firma distinta.
Compara siempre con una función de tiempo constante para evitar ataques de temporización. Si la firma no coincide, descarta la solicitud.
import crypto from 'node:crypto';

function verifyWokuSignature(rawBody, signatureHeader, secret) {
  // signatureHeader: "sha256=<hex>"
  const expected =
    'sha256=' +
    crypto.createHmac('sha256', secret).update(rawBody).digest('hex');

  const a = Buffer.from(signatureHeader);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Ejemplo con Express. Usa el cuerpo crudo, no el JSON parseado.
import express from 'express';
const app = express();

app.post(
  '/webhooks/woku',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const signature = req.header('X-Woku-Signature') ?? '';
    if (!verifyWokuSignature(req.body, signature, process.env.WOKU_WEBHOOK_SECRET)) {
      return res.status(401).send('Invalid signature');
    }
    const event = JSON.parse(req.body.toString('utf8'));
    // ... procesa el evento
    res.status(200).json({ received: true });
  },
);
import hashlib
import hmac

def verify_woku_signature(raw_body: bytes, signature_header: str, secret: str) -> bool:
    # signature_header: "sha256=<hex>"
    expected = "sha256=" + hmac.new(
        secret.encode("utf-8"), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)


# Ejemplo con Flask. request.data es el cuerpo crudo.
from flask import Flask, request, abort, jsonify
import os

app = Flask(__name__)

@app.post("/webhooks/woku")
def woku_webhook():
    signature = request.headers.get("X-Woku-Signature", "")
    if not verify_woku_signature(request.data, signature, os.environ["WOKU_WEBHOOK_SECRET"]):
        abort(401)
    event = request.get_json()
    # ... procesa el evento
    return jsonify(received=True), 200
<?php
function verify_woku_signature(string $rawBody, string $signatureHeader, string $secret): bool
{
    // $signatureHeader: "sha256=<hex>"
    $expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
    return hash_equals($expected, $signatureHeader);
}

$rawBody = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_WOKU_SIGNATURE'] ?? '';
$secret = getenv('WOKU_WEBHOOK_SECRET');

if (!verify_woku_signature($rawBody, $signature, $secret)) {
    http_response_code(401);
    echo 'Invalid signature';
    exit;
}

$event = json_decode($rawBody, true);
// ... procesa el evento

http_response_code(200);
header('Content-Type: application/json');
echo json_encode(['received' => true]);

Respuesta esperada de tu endpoint

Tu endpoint debe responder con un código HTTP 2xx (idealmente 200) lo antes posible. woku considera exitosa la entrega ante cualquier 2xx.
Procesa el evento de forma asíncrona: responde 200 de inmediato y encola el trabajo pesado. woku usa un timeout de 30 segundos por intento; si tu endpoint tarda más, la entrega se cuenta como fallida.

Reintentos y dead-letter

Si tu endpoint no responde con 2xx (o no responde dentro del timeout), woku reintenta con backoff exponencial:
IntentoEspera antes del intento
1inmediato
2~1 s
3~2 s
Tras agotar los 3 intentos, la entrega pasa a estado dead-letter: no se vuelve a intentar automáticamente. Puedes revisar el historial de entregas y reintentar manualmente las que quedaron en dead-letter desde Empresa → Integraciones → Webhooks, en el detalle del webhook.
Como un evento puede entregarse más de una vez (por un reintento sobre una entrega que sí llegó pero respondió tarde), diseña tu endpoint para que sea idempotente: usa el identificador del recurso en data para descartar duplicados.