Webhooks
Recevez des notifications en temps réel sur les événements liés à vos pay links et factures.
Temps réel
Recevez les événements instantanément lorsqu'ils se produisent.
Sécurisé
Signature HMAC-SHA256 pour vérifier l'authenticité des requêtes.
Retry automatique
Jusqu'à 10 tentatives automatiques en cas d'échec, avec backoff exponentiel.
Gestion des endpoints
/v1/webhook-endpointsLister les webhook endpoints configurés
{
"data": [
{
"id": "wh_a1b2c3d4...",
"url": "https://api.exemple.fr/webhooks/chorus-pay",
"enabled": true,
"description": "Production webhook",
"events": {
"pay_link.created": true,
"pay_link.sent": true,
"pay_link.accepted": true,
"invoice.deposited": true,
"invoice.paid": true
},
"auth_configured": false,
"created_at": "2025-09-01T10:00:00Z",
"updated_at": "2025-10-20T14:00:00Z",
"last_triggered_at": "2025-10-22T10:30:00Z"
}
]
}/v1/webhook-endpointsCréer un nouveau webhook endpoint
{
"url": "https://api.exemple.fr/webhooks/chorus-pay",
"description": "Production webhook",
"events": {
"pay_link.created": true,
"pay_link.sent": true,
"pay_link.accepted": true,
"pay_link.cancelled": true,
"invoice.deposited": true,
"invoice.paid": true,
"invoice.rejected": true
"pay_link.rejected": true,
"pay_link.expired": true
},
"auth": {
"username": "mon_utilisateur",
"password": "mon_mot_de_passe"
}
}{
"id": "wh_a1b2c3d4...",
"url": "https://api.exemple.fr/webhooks/chorus-pay",
"secret": "whsec_1234567890abcdef",
"enabled": true,
"description": "Production webhook",
"events": {
"pay_link.created": true,
"pay_link.sent": true,
"pay_link.accepted": true,
"pay_link.cancelled": true,
"invoice.deposited": true,
"invoice.paid": true,
"invoice.rejected": true
},
"created_at": "2025-10-22T10:30:00Z"
}Important : Le secret est affiché une seule fois lors de la création. Stockez-le de manière sécurisée.
auth (optionnel) : Pour les URLs protégées par une authentification HTTP Basic (popup de connexion), fournissez username et password. Le mot de passe n'est jamais renvoyé dans les réponses API.
/v1/webhook-endpoints/:idModifier un webhook endpoint (URL, événements, activation)
{
"events": {
"pay_link.created": true,
"pay_link.accepted": true,
"invoice.paid": true,
"invoice.creation.failed": true
},
"enabled": true
}{
"id": "wh_a1b2c3d4...",
"url": "https://api.exemple.fr/webhooks/chorus-pay",
"enabled": true,
"description": "Production webhook",
"events": {
"pay_link.created": true,
"pay_link.accepted": true,
"invoice.paid": true,
"invoice.creation.failed": true
},
"created_at": "2025-09-01T10:00:00Z",
"updated_at": "2025-10-22T11:00:00Z",
"last_triggered_at": "2025-10-22T10:30:00Z"
}/v1/webhook-endpoints/:idModifier un webhook endpoint
{
"url": "https://api.votil.fr/webhooks/chorus-pay-v2",
"description": "Webhook staging",
"enabled": true,
"events": { "pay_link.created": true },
"auth": {
"username": "user",
"password": "secret"
}
}Pour supprimer l'authentification : "auth": null
/v1/webhook-endpoints/:idSupprimer un webhook endpoint
{
"id": "wh_a1b2c3d4...",
"deleted": true
}/v1/webhook-endpoints/:id/testEnvoyer un événement de test pour vérifier la configuration
Envoie un événement de test à l'endpoint spécifié. L'endpoint doit être activé. La requête attend la livraison et retourne le résultat.
{
"event_id": "evt_a1b2c3d4...",
"webhook_event_id": "whevt_x7y8z9...",
"status": "sent",
"response_status_code": 200,
"sent_at": "2025-10-22T10:30:00Z"
}Événements
Tous les événements que vous pouvez recevoir via webhook. Chaque endpoint peut être configuré pour ne recevoir que les événements souhaités.
Format général
Tous les événements suivent la même structure
{
"id": "evt_a1b2c3d4e5f6...",
"type": "pay_link.accepted",
"created_at": "2025-10-22T10:30:00Z",
"data": {
"object": {
// Données spécifiques à l'événement
}
}
}Pay Link
| Événement | Description |
|---|---|
pay_link.created | Un nouveau pay link a été créé |
pay_link.sent | Le devis a été envoyé par email au client |
pay_link.viewed | Le client a consulté la page de paiement |
pay_link.accepted | Le client a accepté le devis et téléversé les documents d'engagement |
pay_link.purchase_order_uploaded | Un bon de commande a été téléversé via l'API |
pay_link.purchase_order_uploaded_by_client | Un bon de commande a été téléversé par le client |
pay_link.purchase_order_analysed | Le bon de commande a été analysé par IA |
pay_link.status_updated | Le statut du pay link a changé |
pay_link.chorus_status_updated | Le statut Chorus Pro du pay link a été mis à jour |
pay_link.scheduled_date_updated | La date programmée de facturation a été modifiée |
pay_link.marked_as_paid | Le pay link a été marqué comme payé manuellement |
pay_link.cancelled | Le pay link a été annulé par le fournisseur |
pay_link.recycled | Le pay link a été recyclé (action requise → accepté) |
pay_link.rejected | Le pay link a été rejeté avec un motif |
pay_link.expired | Le pay link a expiré sans être accepté |
Facture
| Événement | Description |
|---|---|
invoice.created | Une facture a été créée dans le système (ERP ou interne) |
invoice.deposited | La facture a été déposée sur Chorus Pro |
invoice.validated | La facture a été validée sur Chorus Pro |
invoice.paid | La facture a été payée par l'organisme public |
invoice.rejected | La facture a été rejetée dans Chorus Pro |
invoice.creation.failed | La création de la facture a échoué définitivement après plusieurs tentatives |
Webhook
| Événement | Description |
|---|---|
webhook.test | Événement de test (toujours envoyé, peu importe la configuration) |
Cycle de vie d'un pay link
Voici le flux standard des événements pour un pay link, de la création jusqu'au paiement.
pay_link.createdLe fournisseur crée un pay link
pay_link.sentLe devis est envoyé par email au client
pay_link.viewedLe client ouvre le lien de paiement
pay_link.acceptedLe client accepte et upload le bon de commande
invoice.createdLa facture est créée dans l'ERP
invoice.depositedLa facture est déposée sur Chorus Pro
invoice.validatedChorus Pro valide la facture
invoice.paidL'organisme public effectue le paiement
Événements alternatifs
pay_link.expiredsi le client ne répond pas à tempspay_link.cancelledsi le fournisseur annuleinvoice.rejectedsi Chorus Pro rejette la factureinvoice.creation.failedsi la facturation échoue
Suivi Chorus Pro
L'événement pay_link.chorus_status_updated est émis à chaque changement de statut Chorus Pro, vous permettant de suivre la progression de la facture en temps réel (mise à disposition, mandatée, comptabilisée, mise en paiement).
Exemples de payloads
Chaque événement contient un payload data.object avec les données pertinentes. Voici les payloads des événements les plus courants.
pay_link.accepted
Envoyé lorsque le client accepte le devis
{
"id": "evt_a1b2c3d4e5f6...",
"type": "pay_link.accepted",
"created_at": "2025-10-22T10:30:00Z",
"data": {
"object": {
"pay_link_id": "pl_abc123",
"quote_number": "01163",
"amount": 62.00,
"client_name": "Mairie de Saint-Cloud",
"client_siret": "21092064000016",
"accepted_at": "2025-10-22T10:30:00Z",
"validation_mode": "instant"
}
}
}invoice.deposited
Envoyé lorsque la facture est déposée sur Chorus Pro
{
"id": "evt_x7y8z9...",
"type": "invoice.deposited",
"created_at": "2025-10-22T10:31:00Z",
"data": {
"object": {
"invoice_id": "inv_abc123",
"pay_link_id": "pl_abc123",
"amount": 62.00,
"chorus_reference": "CPP-2025-123456",
"deposited_at": "2025-10-22T10:31:00Z"
}
}
}invoice.paid
Envoyé lorsque le paiement est effectué
{
"id": "evt_p1q2r3...",
"type": "invoice.paid",
"created_at": "2025-11-15T14:20:00Z",
"data": {
"object": {
"invoice_id": "inv_abc123",
"amount": 62.00,
"previous_status": "validated",
"new_status": "paid",
"paid_at": "2025-11-15T14:20:00Z"
}
}
}pay_link.status_updated
Envoyé à chaque changement de statut du pay link
{
"id": "evt_s4t5u6...",
"type": "pay_link.status_updated",
"created_at": "2025-11-15T14:20:00Z",
"data": {
"object": {
"pay_link_id": "pl_abc123",
"quote_number": "01163",
"invoice_id": "inv_abc123",
"chorus_reference": "CPP-2025-123456",
"previous_status": "invoiced",
"new_status": "paid",
"invoice_status": "paid",
"chorus_status": "MISE_EN_PAIEMENT"
}
}
}invoice.creation.failed
Envoyé lorsque la création de la facture échoue définitivement
{
"id": "evt_f1g2h3...",
"type": "invoice.creation.failed",
"created_at": "2025-10-22T12:00:00Z",
"data": {
"object": {
"pay_link_id": "pl_abc123",
"queue_id": "iq_xyz789",
"error": "Chorus Pro API unavailable",
"retry_count": 5
}
}
}Sécurité
Vérification de signature
Chaque requête webhook inclut une signature HMAC-SHA256 dans l'en-tête X-Chorus-Pay-Signature. Vérifiez toujours cette signature pour vous assurer que la requête provient bien de Chorus Pay.
Headers envoyés
| Header | Description |
|---|---|
Content-Type | application/json |
X-Chorus-Pay-Signature | Signature HMAC-SHA256 du body |
User-Agent | Chorus-Pay-Webhooks/1.0 |
Implémentation (Node.js)
const crypto = require('crypto');
function verifyWebhookSignature(req) {
const signature = req.headers['x-chorus-pay-signature'];
const secret = process.env.WEBHOOK_SECRET;
const hmac = crypto.createHmac('sha256', secret);
const expectedSignature = hmac
.update(JSON.stringify(req.body))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
app.post('/webhooks/chorus-pay', (req, res) => {
if (!verifyWebhookSignature(req)) {
return res.status(401).send('Invalid signature');
}
const event = req.body;
switch (event.type) {
case 'pay_link.accepted':
// Traiter l'acceptation du devis
break;
case 'invoice.deposited':
// Facture déposée sur Chorus Pro
break;
case 'invoice.paid':
// Paiement reçu
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.status(200).send('OK');
});Implémentation (Python)
import hmac
import hashlib
import json
def verify_webhook_signature(request, secret):
signature = request.headers.get('X-Chorus-Pay-Signature')
expected_signature = hmac.new(
secret.encode(),
request.data,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
@app.route('/webhooks/chorus-pay', methods=['POST'])
def webhook():
if not verify_webhook_signature(request, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.json
if event['type'] == 'pay_link.accepted':
# Traiter l'acceptation du devis
pass
elif event['type'] == 'invoice.paid':
# Paiement reçu
pass
return 'OK', 200Gestion des retries
En cas d'échec de livraison (timeout de 30s, erreur 5xx, erreur réseau, etc.), nous réessayons automatiquement selon le schéma suivant :
| Tentative | Délai |
|---|---|
| 1 | Immédiate |
| 2 | Après 1 min |
| 3 | Après 2 min |
| 4 | Après 5 min |
| 5 | Après 30 min |
| 6 | Après 2 heures |
| 7 | Après 6 heures |
| 8 | Après 12 heures |
| 9 | Après 24 heures |
| 10 | Après 48 heures |
Après 10 tentatives infructueuses, l'événement est marqué comme échoué. Vous pouvez déclencher un retry manuel depuis l'interface ou via l'API.
Bonne pratique
Votre endpoint doit répondre avec un code HTTP 2xx (idéalement 200) dans les 30 secondes pour que nous considérions la livraison comme réussie. Traitez les webhooks de manière asynchrone pour éviter les timeouts.
/v1/webhook-events/:id/retryRéessayer manuellement un événement webhook échoué
Relance la livraison d'un événement webhook qui a échoué. L'événement ne doit pas être déjà livré avec succès ni avoir dépassé le nombre maximum de tentatives.
{
"id": "whevt_a1b2c3d4...",
"status": "retrying",
"retry_count": 3,
"next_retry_at": "2025-10-22T14:30:00Z",
"last_sent_at": "2025-10-22T12:00:00Z"
}Best Practices
1. Traitez les événements de manière idempotente
En cas de retry, vous pourriez recevoir le même événement plusieurs fois. Utilisez le champ id de l'événement pour détecter les doublons.
2. Répondez rapidement
Retournez un code 200 dès réception, puis traitez l'événement de manière asynchrone (queue, background job, etc.). Le timeout est de 30 secondes.
3. Utilisez HTTPS
Seules les URLs HTTPS sont acceptées pour les webhooks afin de garantir la sécurité des données en transit.
4. Vérifiez toujours la signature
Utilisez le header X-Chorus-Pay-Signature pour valider l'origine de chaque requête avant de la traiter.
5. Abonnez-vous aux événements pertinents
Ne vous abonnez qu'aux événements dont vous avez besoin. Pour la plupart des intégrations, les événements essentiels sont pay_link.accepted, invoice.deposited et invoice.paid.
6. Loggez les événements
Conservez un log des événements reçus pour faciliter le debugging et l'audit. Stockez au minimum l'id, le type et le created_at.