Webhooks
Recebe uma solicitação HTTPS assinada quando algo acontece na conta.
Eventos
| Evento | |
|---|---|
message.replied | A IA respondeu a um cliente no WhatsApp, no chat do site ou pela API. Inclui a mensagem do cliente e a resposta. |
booking.created | A IA marcou uma marcação ou reserva (data, hora, duração, tipo). |
limit.reached | A conta usou todas as interações do plano no período atual. |
knowledge.updated | O Brain foi atualizado pela API (páginas, leitura do site ou FAQ). |
data.channel é whatsapp, web (chat do site) ou api.
Criar um webhook
Cria-o na página Chaves de API ou pela API. O secret só é devolvido uma vez: guarda-o para verificar as assinaturas. Até 10 webhooks por conta; events pode ser uma lista ou ["*"] para todos.
curl https://api.oryksa.com/v1/webhooks \
-H "Authorization: Bearer oryk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/oryksa-webhook", "events": ["message.replied", "booking.created"]}'{
"object": "webhook",
"id": "wh_3f2a9c0d1e7b4a5c8d9e",
"url": "https://example.com/oryksa-webhook",
"events": ["booking.created", "message.replied"],
"active": true,
"secret": "whsec_2b7c... (shown only once)",
"failures": 0
}Conteúdo dos eventos
Todos os eventos têm o mesmo envelope: id, type, created_at e data.
{
"id": "evt_9b1f0d6c2a4e4f7e8a0b1c2d3e4f5a6b",
"type": "message.replied",
"created_at": "2026-09-24T09:41:07.512Z",
"data": {
"channel": "whatsapp",
"contact_id": "[email protected]",
"customer_message": "Do you have parking?",
"reply": "Yes, free parking right next to the winery!"
}
}{
"id": "evt_1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f",
"type": "booking.created",
"created_at": "2026-09-24T09:42:10.004Z",
"data": {
"channel": "web",
"contact_id": "web:1b2a9287-c821-437d-a8b5-7a1bd623bfe5",
"customer_name": "Ana",
"date": "2026-09-27",
"time": "10:00",
"duration": "60 min",
"type": "Winery visit"
}
}{
"id": "evt_...",
"type": "limit.reached",
"created_at": "2026-09-24T10:00:00Z",
"data": { "plan": "free" }
}{
"id": "evt_...",
"type": "knowledge.updated",
"created_at": "2026-09-24T10:05:00Z",
"data": { "source": "pages", "pages": 12, "chars": 48211 }
}Verificar a assinatura
Cada pedido leva ORYKSA-Signature: t=<unix time>,v1=<hex>, em que v1 é o HMAC-SHA256 de t + "." + corpo em bruto com o teu segredo. Confirma também que t é recente (por exemplo, menos de 5 minutos) para bloquear repetições. Outros cabeçalhos: ORYKSA-Event e ORYKSA-Delivery (id do evento, útil para ignorar duplicados).
import crypto from "node:crypto";
import express from "express";
const app = express();
// Keep the RAW body: the signature is computed over the exact bytes.
app.post("/oryksa-webhook", express.raw({ type: "application/json" }), (req, res) => {
const header = req.get("ORYKSA-Signature") || ""; // t=1727170000,v1=abc...
const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
const expected = crypto
.createHmac("sha256", process.env.ORYKSA_WEBHOOK_SECRET)
.update(`${parts.t}.${req.body}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
if (!fresh || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 || ""))) {
return res.status(400).send("bad signature");
}
const event = JSON.parse(req.body);
if (event.type === "booking.created") {
// save the booking in your system
}
res.sendStatus(200);
});import hashlib, hmac, os, time
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["ORYKSA_WEBHOOK_SECRET"].encode()
@app.post("/oryksa-webhook")
def oryksa_webhook():
raw = request.get_data() # exact bytes
parts = dict(p.split("=", 1) for p in request.headers.get("ORYKSA-Signature", "").split(","))
expected = hmac.new(SECRET, f"{parts.get('t')}.".encode() + raw, hashlib.sha256).hexdigest()
if abs(time.time() - int(parts.get("t", 0))) > 300 or not hmac.compare_digest(expected, parts.get("v1", "")):
abort(400)
event = request.get_json()
if event["type"] == "message.replied":
pass # e.g. copy the conversation to your CRM
return "", 200<?php
$raw = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_ORYKSA_SIGNATURE'] ?? ''), $p);
$expected = hash_hmac('sha256', $p['t'] . '.' . $raw, getenv('ORYKSA_WEBHOOK_SECRET'));
if (abs(time() - (int)$p['t']) > 300 || !hash_equals($expected, $p['v1'] ?? '')) {
http_response_code(400); exit;
}
$event = json_decode($raw, true);
http_response_code(200);Entrega e novas tentativas
Responde com qualquer estado 2xx em 10 segundos. Caso contrário a ORYKSA tenta mais 2 vezes (após 2 e 4 segundos). Os redirecionamentos não são seguidos. O último estado HTTP e o número de falhas seguidas aparecem na página Chaves de API e em GET /v1/webhooks. Usa Testar para enviar um evento webhook.test.

