ORYKSAORYKSADéveloppeurs
Obtenir des clés API

Webhooks

Recevez une requête HTTPS signée dès qu'un événement se produit dans le compte.

Événements

Événement
message.repliedL'IA a répondu à un client sur WhatsApp, le chat du site web ou l'API. Inclut le message du client et la réponse.
booking.createdL'IA a réservé un rendez-vous ou une réservation (date, heure, durée, type).
limit.reachedLe compte a utilisé toutes les interactions du forfait pour la période en cours.
knowledge.updatedLe Brain a été mis à jour via l'API (pages, crawl ou FAQ).

data.channel est whatsapp, web (chat du site web) ou api.

Créer un webhook

POST/v1/webhooks

Créez-le sur la page Clés API ou via l'API. Le secret n'est renvoyé qu'une seule fois : conservez-le pour vérifier les signatures. Jusqu'à 10 webhooks par compte ; events peut être une liste ou ["*"] pour tous.

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
}

Payloads

Chaque événement possède la même enveloppe : id, type, created_at et 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 }
}

Vérifiez la signature

Chaque requête contient ORYKSA-Signature: t=<unix time>,v1=<hex>, où v1 est le HMAC-SHA256 de t + "." + raw body avec votre secret. Vérifiez aussi que t est récent (par exemple moins de 5 minutes) pour bloquer les rejets. Autres en-têtes : ORYKSA-Event et ORYKSA-Delivery (identifiant de l'événement, utile pour ignorer les doublons).

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);

Livraison et nouvelles tentatives

Répondez avec un statut 2xx dans les 10 secondes. Sinon, ORYKSA tente à nouveau 2 fois (après 2 et 4 secondes). Les redirections ne sont pas suivies. Le dernier statut HTTP et le nombre d'échecs consécutifs sont visibles sur la page Clés API et dans GET /v1/webhooks. Utilisez Test pour envoyer un événement webhook.test .