Webhooks

Recevez les events de votre tenant en push HTTP — catalogue, vérification de signature HMAC-SHA256, retries, replay.

Les webhooks Architecture Platform livrent en push HTTP les événements qui se produisent dans votre tenant (création d'application, mise à jour, soumission de DAS…). Ils suppriment le besoin de polling sur l'API publique et permettent des intégrations temps-réel avec vos systèmes downstream (n8n, Zapier, Slack via middleware, ETL custom).

Catalogue d'événements (V1)

Trois event types initiaux, couvrant ~80 % des cas d'usage :

Event typeQuandPayload data
application.createdUne Business Application est créée dans le référentiel{ code, name, domaine, type, status }
application.updatedAu moins un champ d'une application a changé{ code, changed_fields: [...] }
das.submittedUn Document d'Architecture Synthétique est soumis{ das_id, app_code }

D'autres events (notice.published, session.scheduled, agent.created, etc.) sont sur la roadmap — ouvrez une demande sur le portail si vous avez besoin de l'un d'eux en priorité.

Configuration côté tenant

Un tenant-admin enregistre une subscription par endpoint :

Cliquez Nouvelle subscription. Saisissez un nom, une URL HTTPS et cochez les event types à recevoir.
Copiez le secret immédiatement — affiché une seule fois. Il sert à signer le payload côté serveur.
Implémentez la vérification de signature côté endpoint (cf. ci-dessous). Sans cette étape vous n'avez aucune garantie que la requête vient bien d'Architecture Platform.

Format du payload

Chaque requête POST envoie un body JSON enveloppé, calqué sur les conventions Stripe / GitHub :

{
  "id":         "evt_550e8400-e29b-41d4-a716-446655440000",
  "type":       "application.created",
  "created_at": "2026-06-25T10:00:00Z",
  "data": {
    "code":    "BAP-001",
    "name":    "Salesforce Sales Cloud",
    "domaine": "CRM",
    "type":    "Application transverse",
    "status":  "ACTIVE"
  }
}

Headers HTTP

Chaque requête POST sortante transporte cinq headers :

HeaderDescription
Content-Typeapplication/json
X-Arch-Signaturehex digest HMAC-SHA256 — voir vérification ci-dessous
X-Arch-Timestampepoch seconds UTC du signing (anti-replay)
X-Arch-Eventle event_type (ex. application.created)
X-Arch-Event-IdUUID idempotency key — identique pour toutes les subscriptions du même event domain
X-Arch-Delivery-IdUUID de cette tentative spécifique (utile pour log / support)

Vérification de la signature

Crucial. Sans vérification, n'importe qui peut envoyer une fausse requête sur votre endpoint.

L'algorithme : HMAC-SHA256(secret, timestamp + "." + body), encodé en hex. Identique au format des webhooks Stripe.

Node.js (TypeScript)

import crypto from "crypto";
import express from "express";

const app = express();
const SECRET = process.env.ARCH_WEBHOOK_SECRET!;

// IMPORTANT : récupérer le body brut, pas le JSON parsé,
// sinon la signature ne match plus (ordre des clés, espacement).
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.header("X-Arch-Signature") ?? "";
  const timestamp = req.header("X-Arch-Timestamp") ?? "";
  const body      = req.body.toString("utf8");

  // Anti-replay : refuser tout timestamp > 5 min de drift.
  const drift = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (drift > 300) return res.status(401).send("timestamp drift");

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

  // Comparaison en temps constant — évite les timing attacks.
  const sigBuf = Buffer.from(signature, "hex");
  const expBuf = Buffer.from(expected, "hex");
  if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) {
    return res.status(401).send("bad signature");
  }

  const event = JSON.parse(body);
  console.log("Received event:", event.type, event.id);
  res.status(200).send("ok");
});

Python (Flask)

import hmac, hashlib, os, time
from flask import Flask, request

app = Flask(__name__)
SECRET = os.environ["ARCH_WEBHOOK_SECRET"]

@app.post("/webhook")
def webhook():
    signature = request.headers.get("X-Arch-Signature", "")
    timestamp = request.headers.get("X-Arch-Timestamp", "0")
    body = request.get_data(as_text=True)

    # Anti-replay
    if abs(time.time() - int(timestamp)) > 300:
        return "timestamp drift", 401

    expected = hmac.new(
        SECRET.encode(),
        f"{timestamp}.{body}".encode(),
        hashlib.sha256,
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        return "bad signature", 401

    event = request.get_json(force=True)
    print(f"Received event: {event['type']} {event['id']}")
    return "ok", 200

Toujours valider la signature avant de traiter le payload. Un endpoint webhook public sans vérification est une porte d'entrée pour de l'injection arbitraire dans votre système downstream.

Idempotence

Chaque event domain produit un seul X-Arch-Event-Id — partagé entre les subscriptions si vous en avez plusieurs. Stockez l'event_id côté consommateur et ignorez les replays (même event_id déjà traité).

CREATE TABLE webhook_events_received (
  event_id    VARCHAR(64) PRIMARY KEY,
  event_type  VARCHAR(64) NOT NULL,
  received_at TIMESTAMP   DEFAULT CURRENT_TIMESTAMP
);

Retries automatiques

Si votre endpoint ne renvoie pas un 2xx (timeout, 4xx, 5xx), Architecture Platform retry avec un backoff exponentiel facteur 5 :

TentativeDélai après l'échec précédent
1 (immédiat)
21 min
35 min
425 min
52 h 05
610 h 25

Au bout de 6 tentatives échouées, l'event passe en status EXHAUSTED (terminal) — il n'est plus retenté automatiquement. Vous pouvez le replay manuellement depuis /admin/webhooks (timeline → bouton « Replay »).

Auto-disable

Après 10 échecs consécutifs sur une même subscription (quel que soit l'event), la subscription passe enabled=false automatiquement pour éviter le DDoS accidentel d'un endpoint cassé. Le tenant-admin doit la ré-activer explicitement après avoir corrigé l'endpoint cible.

Replay manuel

Depuis /admin/webhooks, la timeline « Livraisons récentes » liste les 50 derniers events. Le bouton Replay par ligne :

  1. Repasse l'event en status PENDING
  2. Tente immédiatement la delivery (sans attendre le prochain tick scheduler)
  3. Met à jour le status (DELIVERED / FAILED)

Use case : votre endpoint était KO 30 min pendant une fenêtre de maintenance, vous le ré-activez et voulez ré-envoyer les events manqués sans attendre que le backoff exponentiel les rattrape.

Bonnes pratiques

  • Toujours répondre rapidement (200 OK en < 5 s) — votre endpoint doit accuser réception puis traiter en arrière-plan. Sinon vous risquez un timeout côté Architecture Platform (read timeout = 15 s).
  • Logguer le X-Arch-Delivery-Id — permet à notre support de cross-référencer une tentative depuis votre log si problème.
  • Tester en local avec smee.io ou ngrok — tunneler vers localhost pour développer la vérification de signature avant deploy.
  • Endpoint dédié par event type si la logique métier diverge fortement (sinon un seul endpoint avec un switch sur event.type est plus simple).

Roadmap

  • v0.1.78 — events additionnels : notice.published, session.scheduled, agent.created, agent.updated.
  • v0.1.79 — filtres avancés côté subscription (par domaine, par code application).
  • v0.2 — webhooks "système" globaux pour platform-admins (cross-tenant) : tenant.created, subscription.activated, etc.

On this page