Webhook
Initiative envoie des requêtes HTTP POST à votre serveur chaque fois que des fiches changent. Ce guide couvre tout ce dont vous avez besoin pour recevoir, vérifier et traiter ces requêtes.
Vue d'ensemble
Les webhooks sortants vous permettent de réagir en temps réel aux changements de fiches du CRM. Lorsqu'une fiche est créée, mise à jour ou supprimée dans Initiative, la plateforme identifie les abonnements webhook concernés et envoie immédiatement des notifications aux endpoints abonnés.
Créer un abonnement
POST /api/v1/webhook-subscriptions
Authorization: Bearer <your-api-token>
Content-Type: application/json
{
"name": "My CRM listener",
"target_url": "https://your-server.example.com/hooks/initiative",
"event_types": ["contacts.created", "contacts.updated", "contacts.deleted"]
}
Réponse réussie (201 Created) :
{
"data": {
"id": "42",
"type": "webhook-subscription",
"attributes": {
"name": "My CRM listener",
"target_url": "https://your-server.example.com/hooks/initiative",
"event_types": ["contacts.created", "contacts.updated", "contacts.deleted"],
"status": "active",
"secret": "whsec_a3f2c1...",
"created_at": "2026-05-27T10:00:00+00:00"
}
}
}Conservez le secret immédiatement — il n'est renvoyé qu'une seule fois et sert à vérifier chaque livraison ultérieure.
Format de la charge utile
Chaque livraison est une requête POST JSON dont le corps est structuré comme suit.
Exemple pour contacts.created :
{
"event_type": "contacts.created",
"event_id": "018f2a3b-4c5d-7e8f-9a0b-1c2d3e4f5a6b",
"occurred_at": "2026-05-27T10:01:23.456789+00:00",
"api_version": "2026-05-22",
"record": {
"id": "1234",
"module": "Contacts",
"attributes": {
"firstname": "Jane",
"lastname": "Smith",
"email": "[email protected]"
}
}
}En-têtes envoyés à chaque livraison
| En-tête | Description |
|---|---|
X-Initiative-Event-Id | UUID de l'événement webhook |
X-Initiative-Event-Type | par ex. contacts.created |
X-Initiative-Timestamp | Horodatage Unix (secondes) sous forme de chaîne |
X-Initiative-Signature | sha256=<hmac-sha256> — voir Vérifier la signature |
X-Initiative-Delivery-Id | ID entier de cette tentative de livraison |
X-Initiative-Subscription-Id | ID entier de l'abonnement |
X-Initiative-Attempt-Number | Numéro de tentative (à partir de 1) |
X-Initiative-API-Version | 2026-05-22 |
X-Initiative-Bulk-Operation-Id | (facultatif) UUID si l'événement fait partie d'un import en masse |
Content-Type | application/json; charset=utf-8 |
Vérifier la signature
Chaque livraison inclut un en-tête X-Initiative-Signature. Utilisez-le pour confirmer que la requête provient bien d'Initiative et n'a pas été altérée.
Algorithme : HMAC-SHA256 de la chaîne "{timestamp}.{body}" à l'aide du secret de votre abonnement.
signature = "sha256=" + HMAC_SHA256(key=secret, message=timestamp + "." + raw_body)
PHP
function verifySignature(string $secret, string $timestamp, string $body, string $signature): bool
{
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $secret);
return hash_equals($expected, $signature);
}
// In your request handler:
$timestamp = $_SERVER['HTTP_X_INITIATIVE_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_INITIATIVE_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
if (!verifySignature($secret, $timestamp, $body, $signature)) {
http_response_code(401);
exit;
}Python
import hashlib
import hmac
def verify_signature(secret: str, timestamp: str, body: bytes, signature: str) -> bool:
message = (timestamp + '.' + body.decode('utf-8')).encode('utf-8')
expected = 'sha256=' + hmac.new(secret.encode('utf-8'), message, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)Node.js
const crypto = require('crypto');
function verifySignature(secret, timestamp, body, signature) {
const message = `${timestamp}.${body}`;
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}Ruby
require 'openssl'
def verify_signature(secret, timestamp, body, signature)
message = "#{timestamp}.#{body}"
expected = 'sha256=' + OpenSSL::HMAC.hexdigest('SHA256', secret, message)
Rack::Utils.secure_compare(expected, signature)
endLogique de nouvelle tentative
Si votre endpoint ne répond pas avec un statut 2xx, Initiative réessaie la livraison selon un calendrier à temporisation exponentielle :
| Tentative | Délai après la tentative précédente |
|---|---|
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 heures |
| 6 | 6 heures |
| 7 | 12 heures |
| 8 | 24 heures |
Après la 8ᵉ tentative, la livraison est marquée abandonnée et aucune nouvelle tentative n'est effectuée.
Règle de désactivation automatique
Si un abonnement compte au moins une livraison abandonnée et n'a enregistré aucune livraison réussie dans les 24 heures suivant le premier abandon, l'abonnement est automatiquement désactivé avec disabled_reason: delivery_failures.
Vous devrez le réactiver via l'endpoint PATCH (voir Gérer les abonnements) une fois votre endpoint de nouveau opérationnel.
Protection contre le rejeu
Chaque livraison porte un en-tête X-Initiative-Timestamp contenant l'horodatage Unix (en secondes) auquel la requête a été envoyée.
Pour éviter les attaques par rejeu, rejetez les requêtes dont l'horodatage date de plus de 300 secondes (5 minutes) :
$timestamp = (int)($_SERVER['HTTP_X_INITIATIVE_TIMESTAMP'] ?? 0);
if (abs(time() - $timestamp) > 300) {
http_response_code(400);
exit('Timestamp out of window');
}Vérifiez toujours la signature et la fenêtre d'horodatage ensemble.
Réponses attendues
| Plage de statuts HTTP | Comportement d'Initiative |
|---|---|
2xx | Livraison marquée delivered. Aucune nouvelle tentative. |
3xx | Livraison marquée failed_permanent (redirect_not_followed). Aucune nouvelle tentative. Initiative ne suit jamais les redirections. |
4xx (except 408, 429) | Livraison marquée failed_permanent (non_retryable_4xx). Aucune nouvelle tentative. |
408, 429, 5xx | Livraison planifiée pour une nouvelle tentative (voir Logique de nouvelle tentative). |
| Timeout / no response | Traité comme une erreur de transport ; livraison planifiée pour une nouvelle tentative. |
Votre endpoint doit répondre en moins de 10 secondes. Répondez rapidement (par ex. renvoyez 200 OK immédiatement et traitez de façon asynchrone) pour éviter les nouvelles tentatives.
Types d'événements
Chaque module prend en charge trois actions : created, updated, deleted.
| Type d'événement | Description |
|---|---|
leads.created | Un prospect a été créé |
leads.updated | Un prospect a été mis à jour |
leads.deleted | Un prospect a été supprimé |
contacts.created | Un contact a été créé |
contacts.updated | Un contact a été mis à jour |
contacts.deleted | Un contact a été supprimé |
accounts.created | Un compte a été créé |
accounts.updated | Un compte a été mis à jour |
accounts.deleted | Un compte a été supprimé |
potentials.created | Une opportunité (affaire) a été créée |
potentials.updated | Une opportunité a été mise à jour |
potentials.deleted | Une opportunité a été supprimée |
quotes.created | Un devis a été créé |
quotes.updated | Un devis a été mis à jour |
quotes.deleted | Un devis a été supprimé |
invoices.created | Une facture a été créée |
invoices.updated | Une facture a été mise à jour |
invoices.deleted | Une facture a été supprimée |
purchase-orders.created | Un bon de commande fournisseur a été créé |
purchase-orders.updated | Un bon de commande fournisseur a été mis à jour |
purchase-orders.deleted | Un bon de commande fournisseur a été supprimé |
sales-orders.created | Une commande client a été créée |
sales-orders.updated | Une commande client a été mise à jour |
sales-orders.deleted | Une commande client a été supprimée |
products.created | Un produit a été créé |
products.updated | Un produit a été mis à jour |
products.deleted | Un produit a été supprimé |
services.created | Un service a été créé |
services.updated | Un service a été mis à jour |
services.deleted | Un service a été supprimé |
events.created | Un événement de calendrier a été créé |
events.updated | Un événement de calendrier a été mis à jour |
events.deleted | Un événement de calendrier a été supprimé |
tasks.created | Une tâche a été créée |
tasks.updated | Une tâche a été mise à jour |
tasks.deleted | Une tâche a été supprimée |
helpdesk.created | Un ticket d'assistance a été créé |
helpdesk.updated | Un ticket d'assistance a été mis à jour |
helpdesk.deleted | Un ticket d'assistance a été supprimé |
documents.created | Un document a été créé |
documents.updated | Un document a été mis à jour |
documents.deleted | Un document a été supprimé |
comments.created | Un commentaire a été créé |
comments.updated | Un commentaire a été mis à jour |
comments.deleted | Un commentaire a été supprimé |
Vous pouvez récupérer la liste actuelle par programmation :
GET /api/v1/webhook-event-types
Gérer les abonnements
Lister les abonnements
GET /api/v1/webhook-subscriptions
Récupérer un abonnement
GET /api/v1/webhook-subscriptions/{id}
Mettre à jour un abonnement (name, target_url, event_types, status)
PATCH /api/v1/webhook-subscriptions/{id}
Content-Type: application/json
{
"status": "active"
}
Réactivez un abonnement désactivé en définissant "status": "active".
Supprimer un abonnement
DELETE /api/v1/webhook-subscriptions/{id}
Renouveler le secret
POST /api/v1/webhook-subscriptions/{id}/rotate-secret
Renvoie un nouveau secret. Mettez à jour votre vérification côté serveur immédiatement — l'ancien secret cesse de fonctionner une fois renouvelé.
Lister les livraisons d'un abonnement
GET /api/v1/webhook-subscriptions/{id}/deliveries?page=1&per_page=20
Filtres facultatifs : status, from (ISO 8601), to (ISO 8601).
Récupérer une livraison
GET /api/v1/webhook-deliveries/{id}
Relivrer (rejouer) une livraison
POST /api/v1/webhook-deliveries/{id}/redeliver
Réinitialise la livraison à pending avec attempt_count = 0 afin qu'elle soit redistribuée lors du prochain passage du cron.