Ir al contenido principal
Webhook API 2026-07-26

Webhooks salientes

Eventos firmados que avisan cambios. La REST API conserva la autoridad sobre el estado actual y permite reconciliar entregas perdidas.

Entrega

At-least-once y ordenados por Ticket+endpoint. Deduplica por id y reconcilia contra REST.

Configuracion minima

Crea un endpoint desde el dashboard del agente, selecciona eventos y guarda el secreto para verificar firmas.

  1. Abre Settings / Webhooks en el agente.
  2. Registra una URL HTTPS publica de tu sistema.
  3. Selecciona los eventos que quieres recibir.
  4. Guarda el secreto y verifica cada request con el raw body.

Firma

Oriann firma el string eventId.timestamp.rawBody con HMAC-SHA256.

http
oriann-webhook-id: evt_...
oriann-webhook-timestamp: 1780000000
oriann-webhook-signature: v1=<hex_hmac_sha256>
oriann-webhook-api-version: 2026-07-26
CampoTipoReglaDescripcion
oriann-webhook-idstringrequiredId publico del evento.
oriann-webhook-timestampunix secondsrequiredRechaza requests con mas de 5 minutos.
oriann-webhook-signaturev1=<hex>requiredHMAC-SHA256 calculado con tu secreto.
oriann-webhook-api-version2026-07-26requiredVersion del contrato del payload.
typescript
import crypto from "node:crypto"

export function verifyOriannWebhook({
  rawBody,
  eventId,
  timestamp,
  signatureHeader,
  secret,
}: {
  rawBody: string
  eventId: string
  timestamp: string
  signatureHeader: string
  secret: string
}) {
  if (!/^\d+$/.test(timestamp)) return false
  const ageSeconds = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp))
  if (!Number.isSafeInteger(Number(timestamp)) || ageSeconds > 300) return false

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

  const expectedBytes = Buffer.from(expected, "hex")
  const candidates = signatureHeader
    .split(/\s+/)
    .filter((part) => /^v1=[0-9a-f]{64}$/i.test(part))
    .map((part) => Buffer.from(part.slice(3), "hex"))

  return candidates.some(
    (received) =>
      received.length === expectedBytes.length &&
      crypto.timingSafeEqual(expectedBytes, received)
  )
}

Delivery semantics

Responde 2xx rapido y mueve trabajo pesado a una cola interna.

  • Entrega at-least-once.
  • Orden por Ticket y endpoint; recursos distintos pueden intercalarse.
  • Retries en red, timeout, 429 y 5xx.
  • Cada retry conserva exactamente el mismo id y raw body.
  • Maximo 5 attempts por delivery y timeout de 5 segundos.
  • Maximo 3 endpoints activos con eventos Ticket por agente; 3 fallos consecutivos deshabilitan el endpoint.
  • Secreto anterior valido durante 24 horas al rotar; ambos v1 pueden aparecer en el header durante esa ventana.
  • El secreto solo se muestra al crear o rotar. Si se pierde, rotalo.
  • Tras validar firma y timestamp, deduplica por oriann-webhook-id antes de aplicar efectos para evitar replay.
  • Eventos de prueba usan livemode: false.
  • Los eventos Ticket usan un schema estricto; los eventos historicos no-Ticket conservan propiedades adicionales por compatibilidad.
  • Los objetos dinamicos de Lead se proyectan con un limite de 8 KiB serializados; no se impone un limite artificial de propiedades anidadas.
  • Delivery y retries son transporte; no forman parte del Ticket.

Eventos

Estos son los eventos disponibles hoy. El JSON Schema contiene el enum completo.

lead.createdobject: lead

Lead nuevo detectado por el agente.

json
{
  "id": "evt_0123456789abcdef0123456789abcdef",
  "type": "lead.created",
  "created_at": "2026-07-26T18:00:00.000Z",
  "api_version": "2026-07-26",
  "livemode": true,
  "data": {
    "object": {
      "id": "lead_01J...",
      "agent_id": "agent_01J...",
      "conversation_id": "conversation_01J...",
      "channel": "whatsapp",
      "status": "review",
      "qualification": "good",
      "lead_score": 80,
      "version": 2,
      "created_at": "2026-07-26T18:00:00.000Z",
      "updated_at": "2026-07-26T18:00:00.000Z",
      "lead_definition_id": "lead.services",
      "oad_revision_id": "revision.services.2",
      "oad_definition_digest": "sha256:services",
      "lifecycle_state_id": "lead.new",
      "data": {
        "service": "roof repair"
      },
      "qualification_outputs": {
        "intent": "quote"
      },
      "qualification_outputs_automatic": {
        "intent": "quote"
      },
      "qualification_override": {
        "schemaVersion": "oriann.dev/lead-qualification-override/v2",
        "actorType": "human",
        "actionId": "action.lead.update",
        "originRuleIds": ["rule.lead_activation"],
        "oadRevisionId": "revision.services.2",
        "oadDefinitionDigest": "sha256:services",
        "setAt": 1780000000000,
        "values": {
          "intent": "quote"
        },
        "evidence": {
          "source": "crm"
        }
      },
      "qualification_evaluation": {
        "method": "rules",
        "values": {
          "intent": "quote"
        },
        "outputs": [],
        "missingOutputIds": [],
        "deferredOutputIds": []
      },
      "evidence_resolution": {
        "status": "ready",
        "data": {
          "service": "roof repair"
        },
        "fields": [],
        "missingFieldIds": [],
        "needsConfirmationFieldIds": [],
        "conflictFieldIds": [],
        "rejectedFieldIds": [],
        "candidateStatuses": []
      },
      "evidence_provenance": []
    }
  }
}
lead.updatedobject: lead

Cambio persistido en los datos, calificacion o ciclo de vida de un lead.

json
{
  "id": "evt_0123456789abcdef0123456789abcdef",
  "type": "lead.updated",
  "created_at": "2026-07-26T18:00:00.000Z",
  "api_version": "2026-07-26",
  "livemode": true,
  "data": {
    "object": {
      "id": "lead_01J...",
      "agent_id": "agent_01J...",
      "conversation_id": "conversation_01J...",
      "channel": "whatsapp",
      "status": "review",
      "qualification": "good",
      "lead_score": 80,
      "version": 2,
      "created_at": "2026-07-26T18:00:00.000Z",
      "updated_at": "2026-07-26T18:00:00.000Z",
      "lead_definition_id": "lead.services",
      "oad_revision_id": "revision.services.2",
      "oad_definition_digest": "sha256:services",
      "lifecycle_state_id": "lead.new",
      "data": {
        "service": "roof repair"
      },
      "qualification_outputs": {
        "intent": "quote"
      },
      "qualification_outputs_automatic": {
        "intent": "quote"
      },
      "qualification_override": {
        "schemaVersion": "oriann.dev/lead-qualification-override/v2",
        "actorType": "human",
        "actionId": "action.lead.update",
        "originRuleIds": ["rule.lead_activation"],
        "oadRevisionId": "revision.services.2",
        "oadDefinitionDigest": "sha256:services",
        "setAt": 1780000000000,
        "values": {
          "intent": "quote"
        },
        "evidence": {
          "source": "crm"
        }
      },
      "qualification_evaluation": {
        "method": "rules",
        "values": {
          "intent": "quote"
        },
        "outputs": [],
        "missingOutputIds": [],
        "deferredOutputIds": []
      },
      "evidence_resolution": {
        "status": "ready",
        "data": {
          "service": "roof repair"
        },
        "fields": [],
        "missingFieldIds": [],
        "needsConfirmationFieldIds": [],
        "conflictFieldIds": [],
        "rejectedFieldIds": [],
        "candidateStatuses": []
      },
      "evidence_provenance": []
    }
  }
}
conversation.createdobject: conversation

Se creo una conversacion.

json
{
  "id": "evt_0123456789abcdef0123456789abcdef",
  "type": "conversation.created",
  "created_at": "2026-07-26T18:00:00.000Z",
  "api_version": "2026-07-26",
  "livemode": true,
  "data": {
    "object": {}
  }
}
message.createdobject: message

Se creo un mensaje.

json
{
  "id": "evt_0123456789abcdef0123456789abcdef",
  "type": "message.created",
  "created_at": "2026-07-26T18:00:00.000Z",
  "api_version": "2026-07-26",
  "livemode": true,
  "data": {
    "object": {}
  }
}
conversation.status_updatedobject: conversation

Cambio de estado CRM de una conversacion.

json
{
  "id": "evt_0123456789abcdef0123456789abcdef",
  "type": "conversation.status_updated",
  "created_at": "2026-07-26T18:00:00.000Z",
  "api_version": "2026-07-26",
  "livemode": true,
  "data": {
    "object": {}
  }
}
message.delivery_updatedobject: message

Actualizacion de entrega para un mensaje saliente.

json
{
  "id": "evt_0123456789abcdef0123456789abcdef",
  "type": "message.delivery_updated",
  "created_at": "2026-07-26T18:00:00.000Z",
  "api_version": "2026-07-26",
  "livemode": true,
  "data": {
    "object": {}
  }
}
ticket.createdobject: ticket

Se creo un Ticket y se entrega su Snapshot completo.

json
{
  "id": "evt_0123456789abcdef0123456789abcdef",
  "type": "ticket.created",
  "createdAt": "2026-07-26T18:00:00.000Z",
  "apiVersion": "2026-07-26",
  "livemode": true,
  "data": {
    "object": {
      "object": "ticket",
      "id": "ticket_01J...",
      "agentId": "agent_01J...",
      "conversationId": "conversation_01J...",
      "flowId": "quote_request",
      "status": "open",
      "ticketVersion": 1,
      "attributes": [
        {
          "id": "customer_name",
          "type": "string",
          "value": "Ana",
          "queryable": true
        }
      ],
      "outcome": null,
      "createdAt": "2026-07-26T18:00:00.000Z",
      "updatedAt": "2026-07-26T18:00:00.000Z",
      "resolvedAt": null
    }
  }
}
ticket.updatedobject: ticket

Cambio operativo con su nuevo Snapshot completo.

json
{
  "id": "evt_0123456789abcdef0123456789abcdef",
  "type": "ticket.updated",
  "createdAt": "2026-07-26T18:00:00.000Z",
  "apiVersion": "2026-07-26",
  "livemode": true,
  "data": {
    "object": {
      "object": "ticket",
      "id": "ticket_01J...",
      "agentId": "agent_01J...",
      "conversationId": "conversation_01J...",
      "flowId": "quote_request",
      "status": "open",
      "ticketVersion": 1,
      "attributes": [
        {
          "id": "customer_name",
          "type": "string",
          "value": "Ana",
          "queryable": true
        }
      ],
      "outcome": null,
      "createdAt": "2026-07-26T18:00:00.000Z",
      "updatedAt": "2026-07-26T18:00:00.000Z",
      "resolvedAt": null
    }
  }
}
ticket.resolvedobject: ticket

El Ticket quedo resuelto.

json
{
  "id": "evt_0123456789abcdef0123456789abcdef",
  "type": "ticket.resolved",
  "createdAt": "2026-07-26T18:00:00.000Z",
  "apiVersion": "2026-07-26",
  "livemode": true,
  "data": {
    "object": {
      "object": "ticket",
      "id": "ticket_01J...",
      "agentId": "agent_01J...",
      "conversationId": "conversation_01J...",
      "flowId": "quote_request",
      "status": "resolved",
      "ticketVersion": 1,
      "attributes": [
        {
          "id": "customer_name",
          "type": "string",
          "value": "Ana",
          "queryable": true
        }
      ],
      "outcome": null,
      "createdAt": "2026-07-26T18:00:00.000Z",
      "updatedAt": "2026-07-26T18:00:00.000Z",
      "resolvedAt": "2026-07-26T18:00:00.000Z"
    }
  }
}
ticket.reopenedobject: ticket

El Ticket fue reabierto.

json
{
  "id": "evt_0123456789abcdef0123456789abcdef",
  "type": "ticket.reopened",
  "createdAt": "2026-07-26T18:00:00.000Z",
  "apiVersion": "2026-07-26",
  "livemode": true,
  "data": {
    "object": {
      "object": "ticket",
      "id": "ticket_01J...",
      "agentId": "agent_01J...",
      "conversationId": "conversation_01J...",
      "flowId": "quote_request",
      "status": "reopened",
      "ticketVersion": 1,
      "attributes": [
        {
          "id": "customer_name",
          "type": "string",
          "value": "Ana",
          "queryable": true
        }
      ],
      "outcome": null,
      "createdAt": "2026-07-26T18:00:00.000Z",
      "updatedAt": "2026-07-26T18:00:00.000Z",
      "resolvedAt": null
    }
  }
}
ticket.deletedobject: ticket_tombstone

El Ticket fue eliminado; se entrega un tombstone.

json
{
  "id": "evt_0123456789abcdef0123456789abcdef",
  "type": "ticket.deleted",
  "createdAt": "2026-07-26T18:00:00.000Z",
  "apiVersion": "2026-07-26",
  "livemode": true,
  "data": {
    "object": {
      "object": "ticket_tombstone",
      "id": "ticket_01J...",
      "deletedAt": "2026-07-26T18:00:00.000Z"
    }
  }
}