Webhooks calendrier
Recevez les événements de tous vos events sur une seule URL — avec signature HMAC.
Dans cet article
/calendars/:slug/webhookscreateCalendarWebhookTry It! → /api/events/:eventId/webhooks
Chemin actuel : /api/events/:eventId/webhooks
Paramètres
Modifiez les valeurs puis cliquez Try It! à droite.
slugpathrequisSegment d'URL :slug
eventIdpathrequisRequis pour Try It! (backend actuel — /events/:eventId/webhooks)
AuthorizationheaderrequisToken Bearer Clerk (session dashboard)
Corps (JSON)
Pourquoi un webhook calendrier ?
Avant Plus, les webhooks se configuraient événement par événement. Avec le webhook calendrier, une seule URL reçoit les notifications de tous les events du calendrier — idéal pour Zapier, Make ou un micro-service central.
Statut : Divergent
Aujourd’hui le backend expose /api/events/:eventId/webhooks (auth Clerk, scope événement). La cible Plus est /api/calendars/:slug/webhooks. Les en-têtes et payloads convergeront — voir ci-dessous.
Actuel vs cible
| Aspect | Actuel (dashboard) | Cible (Plus) | |
|---|---|---|---|
| ≠ | Scope | Par événement | Par calendrier |
| ≠ | Création | POST /api/events/:id/webhooks | POST /api/calendars/:slug/webhooks |
| ≠ | Auth config | Bearer Clerk | Bearer dashboard |
| ≠ | Header signature | X-Njanggo-Webhook-Signature: t=…,v1=… | X-Njanggo-Signature: sha256=… |
| ≠ | Header type | X-Njanggo-Webhook-Event | X-Njanggo-Event |
| ○ | Header delivery | — | X-Njanggo-Delivery-Id (idempotence) |
| ○ | Payload calendarSlug | Absent | Présent sur chaque delivery |
| ○ | Logs + retry API | — | GET /api/v1/webhook-deliveries (Pro) |
Types d'événements
| Type | Déclenché quand | Cas d'usage |
|---|---|---|
| guest.registered | Inscription (confirmée, en attente ou waitlist) | CRM, Slack, Google Sheet |
| checkin.created | Check-in à l'entrée | Compteur live, écran géant |
| event.updated | Modification titre, date, lieu… | Sync site externe |
| event.cancelled | Annulation par l'organisateur | Alerte équipe, remboursements auto |
| announcement.sent | Annonce e-mail envoyée | Audit communications |
| reminder.sent | Rappel automatique ou manuel envoyé | Suivi campagnes |
En-têtes HTTP (livraison entrante)
Njanggo envoie une requête POST vers votre URL avec les en-têtes suivants (cible Plus) :
En-têtes cible
Content-Typestringrequisapplication/json
X-Njanggo-EventstringrequisType — ex. guest.registered
X-Njanggo-Delivery-Idstringrequisdel_* — idempotence, ignorer les doublons
X-Njanggo-Signaturestringrequissha256=<hex> — HMAC-SHA256 du corps brut UTF-8
User-AgentstringoptionnelNjanggo-Webhook/1.0
X-Njanggo-Webhook-Signature: t=<unix>,v1=<hex> (HMAC du string {timestamp}.{body}) et X-Njanggo-Webhook-Event. Migration documentée dans le changelog — les deux formats coexisteront brièvement.Signature HMAC
Cible Plus — calculez HMAC-SHA256(secret, rawBody) en hex, comparez en temps constant avec X-Njanggo-Signature (prefixe sha256=).
import crypto from 'crypto';
function verifyWebhook(rawBody: string, signature: string, secret: string) {
const expected = crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex');
const received = signature.replace(/^sha256=/, '');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}function verifyLegacy(rawBody: string, header: string, secret: string) {
const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));
const payload = `${parts.t}.${rawBody}`;
const expected = crypto.createHmac('sha256', secret).update(payload, 'utf8').digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ''));
}Créer un webhook
Aujourd’hui (live)
POST /api/events/:eventId/webhooks (Bearer). La cible calendrier ci-dessous est en déploiement.Corps POST /events/:eventId/webhooks (actuel)
urlurirequisURL HTTPS (Zapier, Make, serveur)
eventsstring[]optionnelguest.registered, checkin.created, event.updated, …
secretstringoptionnelOptionnel — whsec_… généré si omis (affiché une fois)
Corps POST /calendars/:slug/webhooks (cible Plus)
urlurirequisURL HTTPS de votre endpoint (Zapier, Make, serveur)
eventsstring[]optionnelSous-ensemble des 6 types. Défaut : tous.
secretstringoptionnelSecret HMAC custom. Si omis, Njanggo génère whsec_… (affiché une fois).
Requête
{
"url": "https://hooks.zapier.com/hooks/catch/123456/abcdef/",
"events": ["guest.registered", "checkin.created"]
}Réponse
201{
"data": {
"id": "wh_cal_01",
"url": "https://hooks.zapier.com/hooks/catch/123456/abcdef/",
"events": ["guest.registered", "checkin.created"],
"isActive": true,
"plainSecret": "whsec_a1b2c3d4e5f6...",
"createdAt": "2026-07-04T10:00:00.000Z"
}
}Endpoints
Actuel (live) — par événement
/events/:eventId/webhooksLister
/events/:eventId/webhooksCréer
/events/:eventId/webhooks/:idModifier
/events/:eventId/webhooks/:idSupprimer
/events/:eventId/webhooks/:id/testTest
Cible Plus — par calendrier
/calendars/:slug/webhooksLister les webhooks du calendrier
/calendars/:slug/webhooksCréer
/calendars/:slug/webhooks/:idModifier url, events, isActive
/calendars/:slug/webhooks/:idSupprimer
/calendars/:slug/webhooks/:id/testEnvoyer un événement test
Exemples de payloads
guest.registered
Webhook entrant
200{
"id": "del_8f2a1b3c",
"type": "guest.registered",
"createdAt": "2026-07-04T18:30:00.000Z",
"calendarSlug": "mon-collectif",
"data": {
"eventId": "evt_abc123",
"eventSlug": "soiree-jazz",
"eventTitle": "Soirée Jazz",
"guestId": "gst_xyz789",
"userId": "usr_456",
"guestEmail": "alice@example.com",
"guestName": "Alice Martin",
"status": "CONFIRMED",
"ticketId": "tkt_standard",
"ticketName": "Entrée standard",
"registrationAnswers": [
{ "question": "Organisation", "answer": "Association Lumière" }
]
}
}checkin.created
Webhook entrant
200{
"id": "del_9a3b2c1d",
"type": "checkin.created",
"createdAt": "2026-07-04T20:15:00.000Z",
"calendarSlug": "mon-collectif",
"data": {
"eventId": "evt_abc123",
"eventSlug": "soiree-jazz",
"guestId": "gst_xyz789",
"guestName": "Alice Martin",
"guestEmail": "alice@example.com",
"qrCode": "NJ-ABC123XYZ",
"checkedInAt": "2026-07-04T20:15:00.000Z",
"checkedInBy": "usr_staff_01"
}
}WebhookDelivery (logs Pro)
Chaque tentative de livraison crée un objet WebhookDelivery consultable via l’API Pro (Preview). Voir schéma WebhookDelivery.
GET /v1/webhook-deliveries/:id
200{
"data": {
"id": "del_8f2a1b3c",
"webhookId": "wh_cal_01",
"eventType": "guest.registered",
"status": "retrying",
"attempts": 2,
"responseCode": 503,
"nextRetryAt": "2026-07-04T18:35:00.000Z",
"payload": { "type": "guest.registered", "data": { "..." : "..." } }
}
}/v1/webhook-deliveriesFiltrer par webhookId, status, eventType
/v1/webhook-deliveries/:id/retryRelance manuelle
Retry & idempotence
Si votre endpoint ne répond pas HTTP 2xx sous 10 secondes, Njanggo retente jusqu'à 3 fois (délais : 1 min, 5 min, 30 min). Utilisez X-Njanggo-Delivery-Id pour ignorer les doublons.
| Code réponse | Comportement |
|---|---|
| 2xx | Succès — pas de retry |
| 410 Gone | Webhook désactivé automatiquement |
| 429 / 5xx | Retry planifié |
| Timeout 10s | Retry planifié |
Étapes suivantes