Webhooks
Être prévenu par un appel à votre adresse quand une fiche change — s'abonner, vérifier la signature, répondre.
Un webhook appelle votre adresse quand un événement touche une fiche : un contact créé, un parcours terminé, un email ouvert… Plutôt que de relire une fiche pour voir si elle a changé, vous êtes prévenu. Le guide Recevoir un webhook montre un abonnement de bout en bout. Une application ouverte au nom d'un utilisateur — une application mobile, par exemple — reçoit plutôt les messages de ses conversations en temps réel.
S'abonner
POST https://staging-api.docage.com/Webhooks
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…
Content-Type: application/json
{
"Name": "Parcours vers notre CRM",
"Url": "https://crm.example.com/hooks/parcours",
"RecordTypeName": "Transaction",
"Events": [20],
"Scope": 0
}curl -X POST "https://staging-api.docage.com/Webhooks" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…" \
-H "Content-Type: application/json" \
-d '{
"Name": "Parcours vers notre CRM",
"Url": "https://crm.example.com/hooks/parcours",
"RecordTypeName": "Transaction",
"Events": [20],
"Scope": 0
}'using HttpClient client = new();
using HttpRequestMessage request = new(HttpMethod.Post, "https://staging-api.docage.com/Webhooks");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
request.Content = new StringContent("""
{
"Name": "Parcours vers notre CRM",
"Url": "https://crm.example.com/hooks/parcours",
"RecordTypeName": "Transaction",
"Events": [
20
],
"Scope": 0
}
""", Encoding.UTF8, "application/json");
using HttpResponseMessage response = await client.SendAsync(request);
string result = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
throw new HttpRequestException(result);const response = await fetch("https://staging-api.docage.com/Webhooks", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"Name": "Parcours vers notre CRM",
"Url": "https://crm.example.com/hooks/parcours",
"RecordTypeName": "Transaction",
"Events": [20],
"Scope": 0
})
});
const text = await response.text();
if (!response.ok) throw new Error(text);
const result = text ? JSON.parse(text) : null;RecordTypeName: le type de fiche écouté ;GET https://staging-api.docage.com/Webhooks/Catalogliste les types possibles et les événements de chacun.Events: un ou plusieurs événements —1création,2modification,0suppression (mise à la corbeille comprise),3changement d'état (une restauration depuis la corbeille, un blocage…),20changement de statut d'un parcours,30changement de statut d'un email ;10à12pour ce qu'on range dans un classeur ou qu'on en retire.Scope:0toutes les fiches du type,1les fiches tirées d'un modèle,2une seule fiche ;ScopeRecordIddésigne le modèle ou la fiche.WatchedFieldsrestreint une modification aux champs qui comptent ([{ "FieldName": "SignatureStatus", "ToValue": "Done" }]) ;Condition, un filtre de liste écrit en texte JSON, à certaines fiches — voir Être prévenu quand un parcours est signé.PayloadMode:0la charge complète (d'office) ;1la charge légère, sans la fiche — voir ci-dessous.- L'adresse est en HTTPS.
Le webhook ne reçoit que ce que son auteur voit : la fiche envoyée est projetée avec ses droits.
Ce que vous recevez
Un POST à votre adresse, en JSON. En charge complète (PayloadMode à 0, d'office), l'enveloppe porte la fiche :
{
"id": "0f7c2a1e-0000-4000-8000-000000000091",
"type": "Transaction.TransactionStatusChange",
"timestamp": "2026-11-04T09:12:05.000Z",
"webhook": { "id": "6a1b2c3d-0000-4000-8000-000000000090", "name": "Parcours vers notre CRM" },
"organizationId": "5c6d7e8f-0000-4000-8000-000000000042",
"entity": { "type": "Transaction", "id": "cccccccc-0000-4000-8000-000000000003", "name": "Contrat de prestation" },
"changes": { "GlobalStatus": { "from": "Active", "to": "Done" } },
"data": { "Id": "cccccccc-0000-4000-8000-000000000003", "GlobalStatus": 3 }
}changes dit les propriétés modifiées, de l'ancienne valeur à la nouvelle, en texte — une énumération par son nom (Active,
Done) ; data est la fiche, telle que l'API la rend, ses énumérations en nombres (abrégée ici).
En charge légère (PayloadMode à 1), l'enveloppe est la même, sans la fiche : data vaut null.
{
"id": "0f7c2a1e-0000-4000-8000-000000000092",
"type": "Transaction.TransactionStatusChange",
"timestamp": "2026-11-04T09:12:05.000Z",
"webhook": { "id": "6a1b2c3d-0000-4000-8000-000000000093", "name": "Parcours vers notre CRM, charge légère" },
"organizationId": "5c6d7e8f-0000-4000-8000-000000000042",
"entity": { "type": "Transaction", "id": "cccccccc-0000-4000-8000-000000000003", "name": "Contrat de prestation" },
"changes": { "GlobalStatus": { "from": "Active", "to": "Done" } },
"data": null
}Relisez alors la fiche par son identifiant, entity.id :
GET https://staging-api.docage.com/Transactions/cccccccc-0000-4000-8000-000000000003
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…curl "https://staging-api.docage.com/Transactions/cccccccc-0000-4000-8000-000000000003" \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9…"using HttpClient client = new();
using HttpRequestMessage request = new(HttpMethod.Get, "https://staging-api.docage.com/Transactions/cccccccc-0000-4000-8000-000000000003");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
using HttpResponseMessage response = await client.SendAsync(request);
string result = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
throw new HttpRequestException(result);const response = await fetch("https://staging-api.docage.com/Transactions/cccccccc-0000-4000-8000-000000000003", {
headers: {
Authorization: `Bearer ${token}`
}
});
const text = await response.text();
if (!response.ok) throw new Error(text);
const result = text ? JSON.parse(text) : null;Vous lisez ainsi la fiche telle qu'elle est au moment où vous la traitez, avec les droits de votre jeton plutôt qu'avec ceux de
l'auteur du webhook ; une réponse null dit que ce jeton ne la voit pas, ou qu'elle n'existe plus. Une suppression, mise à la
corbeille comprise, arrive toujours sans data, quel que soit le mode.
La référence de l'API décrit chacun de ces appels, type de fiche par type de fiche : les types d'événement qu'il peut porter,
l'enveloppe et la fiche, typée. Ils sont dans la section webhooks de son document, https://staging-api.docage.com/openapi/v2-fr.json, et chacun dans le
document du contrôleur de sa fiche, comme https://staging-api.docage.com/openapi/v2-fr/Contacts.json. Dans la référence, chacun paraît dans le groupe de
sa fiche : « Être prévenu des changements des contacts ».
Vérifier la signature
Chaque appel porte trois en-têtes, selon la spécification Standard Webhooks :
webhook-id: l'identifiant de l'envoi, le même d'une tentative à l'autre ;webhook-timestamp: l'heure de la tentative, en secondes Unix ;webhook-signature:v1,suivi de la signature, en base64.
Il porte aussi webhook-attempt, le numéro de la tentative à partir de 1, et User-Agent: StandardWebhooks/1.0. Un webhook à
jeton partagé envoie en plus Authorization: Bearer suivi de son jeton ; ses en-têtes personnalisés s'ajoutent à l'appel.
La signature est un HMAC-SHA256 de {webhook-id}.{webhook-timestamp}.{corps}, le corps tel que reçu, octet pour octet. Sa clé
est le secret du webhook (whsec_…, à lire par POST https://staging-api.docage.com/Webhooks/{id}/Secret/Reveal), sans son préfixe, décodé du base64.
Refusez un appel dont la signature ne se vérifie pas, ou dont l'heure s'écarte de plus de cinq minutes de la vôtre. Les
bibliothèques Standard Webhooks font tout cela en une ligne.
Après un renouvellement du secret (POST https://staging-api.docage.com/Webhooks/{id}/Secret/Rotate), l'ancien signe encore 24 heures :
webhook-signature porte alors deux signatures, séparées par une espace ; acceptez la première qui se vérifie.
Répondre
Répondez vite, par un code 2xx, et traitez ensuite : sans réponse en 20 secondes, la tentative est un échec, comme tout autre
code :
- l'envoi est retenté, à intervalles croissants : dix tentatives en tout, sur près de quatre jours ; un
429avecRetry-Afterest respecté ; - une redirection (
3xx) est un échec, jamais suivie ; 410met le webhook en panne aussitôt, comme 72 heures d'échecs continus : ses envois sont retenus, son auteur est prévenu, etPOST https://staging-api.docage.com/Webhooks/{id}/Reactivatele remet en service — avec{ "ReplayHeld": true }, ses envois retenus repartent ; sans lui, ils sont annulés.
Un envoi peut arriver deux fois : ignorez un webhook-id déjà traité. L'historique des envois d'un webhook se lit par
GET https://staging-api.docage.com/Webhooks/{id}/Deliveries ; POST https://staging-api.docage.com/Webhooks/{id}/Test en tente un tout de suite.