Skip to main content

Webhooks

Les webhooks sont en cours de conception — non exposés en production. Cette page décrit le modèle prévu pour permettre aux intégrateurs de préparer leurs systèmes.

Vision

Permettre aux recruteurs et intégrateurs tiers (ATS, CRM, Slack, Discord) de recevoir des événements en push sans polling.

Événements prévus

ÉvénementDéclencheurScope
offre.publishedOffre passe à PUBLISHEDRecruteur
offre.closedOffre ferméeRecruteur
candidature.createdNouveau candidat sur une offreRecruteur
candidature.status_changedTransition pipelineRecruteur
candidat.hiredCandidature → HIREDRecruteur
recruiter.approvedAdmin valide un recruteurAdmin
recruiter.suspendedSanction appliquéeAdmin
alert.triggeredNouveau match d’alerteCandidat (email seulement en v1)
blog.comment.flaggedCommentaire signalé > 3 foisAdmin

Modèle de payload

{
  "id": "evt_01HX3R...",
  "type": "candidature.created",
  "createdAt": "2026-04-18T10:00:00Z",
  "data": {
    "candidatureId": 142,
    "offreId": 55,
    "candidatId": 17,
    "statut": "NEW"
  },
  "deliveryAttempt": 1
}
Headers HTTP envoyés :
POST /your-webhook HTTP/1.1
Content-Type: application/json
X-Hive-Event: candidature.created
X-Hive-Delivery: dlv_01HX3R...
X-Hive-Signature: t=1713441600,v1=a1b2c3...
User-Agent: HiveWebhooks/1.0

Signature HMAC

Vérification (Node.js)

import crypto from 'crypto';

function verifyHiveSignature(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');
  const valid = crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(parts.v1),
  );
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
  return valid && fresh;
}

Vérification (Python)

import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=") for p in header.split(","))
    expected = hmac.new(secret.encode(),
                        f"{parts['t']}.{raw_body.decode()}".encode(),
                        hashlib.sha256).hexdigest()
    fresh = abs(time.time() - int(parts["t"])) < 300
    return hmac.compare_digest(expected, parts["v1"]) and fresh

Politique de retry

TentativeDélai
1immédiat
230 s
32 min
410 min
530 min
61 h
74 h
812 h
924 h
1048 h
Timeout par tentative : 10 secondes.

Endpoints prévus

POST   /v1/api/webhooks/subscriptions
GET    /v1/api/webhooks/subscriptions
GET    /v1/api/webhooks/subscriptions/{id}
PATCH  /v1/api/webhooks/subscriptions/{id}
DELETE /v1/api/webhooks/subscriptions/{id}

GET    /v1/api/webhooks/deliveries?subscriptionId=123
POST   /v1/api/webhooks/deliveries/{id}/replay
GET    /v1/api/webhooks/events  # liste des types disponibles

Exemple de souscription

POST /v1/api/webhooks/subscriptions
{
  "url": "https://ats.example.com/hive-webhook",
  "events": ["candidature.created", "candidature.status_changed"],
  "active": true,
  "description": "ATS intégration Tech Corp"
}
Réponse avec secret à stocker :
{
  "id": 12,
  "url": "https://ats.example.com/hive-webhook",
  "events": [...],
  "secret": "whsec_abc123...",
  "active": true
}

Alternative actuelle : polling

En attendant les webhooks, utiliser : Fréquence recommandée : 60 secondes minimum (rate limit oblige).

Roadmap

Voir aussi