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êteDescription
X-Initiative-Event-IdUUID de l'événement webhook
X-Initiative-Event-Typepar ex. contacts.created
X-Initiative-TimestampHorodatage Unix (secondes) sous forme de chaîne
X-Initiative-Signaturesha256=<hmac-sha256> — voir Vérifier la signature
X-Initiative-Delivery-IdID entier de cette tentative de livraison
X-Initiative-Subscription-IdID entier de l'abonnement
X-Initiative-Attempt-NumberNuméro de tentative (à partir de 1)
X-Initiative-API-Version2026-05-22
X-Initiative-Bulk-Operation-Id(facultatif) UUID si l'événement fait partie d'un import en masse
Content-Typeapplication/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)
end

Logique de nouvelle tentative

Si votre endpoint ne répond pas avec un statut 2xx, Initiative réessaie la livraison selon un calendrier à temporisation exponentielle :

TentativeDélai après la tentative précédente
21 minute
35 minutes
430 minutes
52 heures
66 heures
712 heures
824 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 HTTPComportement d'Initiative
2xxLivraison marquée delivered. Aucune nouvelle tentative.
3xxLivraison 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, 5xxLivraison planifiée pour une nouvelle tentative (voir Logique de nouvelle tentative).
Timeout / no responseTraité 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énementDescription
leads.createdUn prospect a été créé
leads.updatedUn prospect a été mis à jour
leads.deletedUn prospect a été supprimé
contacts.createdUn contact a été créé
contacts.updatedUn contact a été mis à jour
contacts.deletedUn contact a été supprimé
accounts.createdUn compte a été créé
accounts.updatedUn compte a été mis à jour
accounts.deletedUn compte a été supprimé
potentials.createdUne opportunité (affaire) a été créée
potentials.updatedUne opportunité a été mise à jour
potentials.deletedUne opportunité a été supprimée
quotes.createdUn devis a été créé
quotes.updatedUn devis a été mis à jour
quotes.deletedUn devis a été supprimé
invoices.createdUne facture a été créée
invoices.updatedUne facture a été mise à jour
invoices.deletedUne facture a été supprimée
purchase-orders.createdUn bon de commande fournisseur a été créé
purchase-orders.updatedUn bon de commande fournisseur a été mis à jour
purchase-orders.deletedUn bon de commande fournisseur a été supprimé
sales-orders.createdUne commande client a été créée
sales-orders.updatedUne commande client a été mise à jour
sales-orders.deletedUne commande client a été supprimée
products.createdUn produit a été créé
products.updatedUn produit a été mis à jour
products.deletedUn produit a été supprimé
services.createdUn service a été créé
services.updatedUn service a été mis à jour
services.deletedUn service a été supprimé
events.createdUn événement de calendrier a été créé
events.updatedUn événement de calendrier a été mis à jour
events.deletedUn événement de calendrier a été supprimé
tasks.createdUne tâche a été créée
tasks.updatedUne tâche a été mise à jour
tasks.deletedUne tâche a été supprimée
helpdesk.createdUn ticket d'assistance a été créé
helpdesk.updatedUn ticket d'assistance a été mis à jour
helpdesk.deletedUn ticket d'assistance a été supprimé
documents.createdUn document a été créé
documents.updatedUn document a été mis à jour
documents.deletedUn document a été supprimé
comments.createdUn commentaire a été créé
comments.updatedUn commentaire a été mis à jour
comments.deletedUn 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.