Swimlane
Retour au centre d'aide

Contenu des webhooks et vérification de la signature

Cette page décrit précisément ce que Swimlane envoie à votre URL de webhook et comment vérifier qu'une requête vient bien de Swimlane. Pour créer un webhook, voir Configurer des webhooks.

La requête

Swimlane envoie un POST avec un corps JSON et ces en-têtes :

  • Content-Type: application/json
  • X-Webhook-Event : le nom de l'événement, par exemple contact.updated
  • X-Webhook-Timestamp : l'heure d'envoi de cette tentative (ISO 8601)
  • X-Webhook-Signature : uniquement si le webhook a une clé secrète

Le corps

{
  "event": "contact.updated",
  "timestamp": "2026-09-14T09:12:03.412+00:00",
  "data": {
    "contactId": 4821,
    "uuid": "3f0c5b1e-8d2a-4c1e-9f5b-2a7d6e1c9b40",
    "firstName": "Julie",
    "lastName": "Martin",
    "email": null,
    "linkedinId": "julie-martin-42",
    "changes": ["note"]
  }
}
  • event : contact.created, contact.updated ou contact.deleted.
  • data.uuid : l'identifiant du contact dans l'API publique. Utilisez-le comme clé dans votre CRM ou votre base.
  • data.contactId : un numéro interne, ne vous appuyez pas dessus.
  • data.firstName, data.lastName, data.email : peuvent valoir null. La plupart des contacts LinkedIn n'ont pas d'e-mail.
  • data.linkedinId : l'identifiant LinkedIn. Le profil s'ouvre à l'adresse https://www.linkedin.com/in/<linkedinId>.
  • data.changes : uniquement sur contact.updated. Il liste les champs envoyés avec la mise à jour. Quand elle vient d'un import ou de la synchronisation de la boîte de réception, il peut contenir des champs dont la valeur n'a pas changé : prenez-le comme une indication, pas comme un différentiel exact. Il ne contient jamais les anciennes valeurs.

Le contenu n'inclut ni l'entreprise, ni le titre, ni la localisation, ni les tags, listes, étape de pipeline, messages ou adéquation ICP. Pour en savoir plus, appelez l'API publique avec l'uuid :

curl "https://api.getswimlane.com/v1/contacts/3f0c5b1e-8d2a-4c1e-9f5b-2a7d6e1c9b40" \
  -H "Authorization: Bearer wpa_votre_token"

Pour un événement contact.deleted, cet appel répond 404 : le contact n'existe déjà plus. Gardez ce dont vous avez besoin à partir du corps du webhook.

L'événement de test

Le test envoyé à la création d'un webhook, ou avec Tester le webhook, ressemble à ceci :

{
  "event": "test",
  "timestamp": "2026-09-14T09:10:00.000+00:00",
  "data": { "message": "This is a test webhook from Swimlane" }
}

Filtrez-le (event vaut test) pour qu'il ne crée pas une fiche vide dans votre CRM.

Vérifier la signature

Quand le webhook a une clé secrète, X-Webhook-Signature contient le HMAC-SHA256 du corps exact de la requête, calculé avec votre clé et écrit en hexadécimal minuscule (sans préfixe sha256=). L'horodatage fait partie du corps signé, et les deux sont recalculés à chaque tentative.

Pour vérifier :

  1. Lisez le corps brut, avant tout décodage JSON. Réencoder un JSON décodé peut modifier les octets et fausser la vérification.
  2. Calculez le HMAC-SHA256 de ce corps brut avec votre clé, en hexadécimal.
  3. Comparez-le à l'en-tête avec une comparaison à temps constant. Rejetez la requête s'ils diffèrent.

Node.js (Express) :

import crypto from 'node:crypto'
import express from 'express'

const app = express()
const secret = process.env.SWIMLANE_WEBHOOK_SECRET

app.post('/swimlane', express.raw({ type: 'application/json' }), (req, res) => {
  const received = req.get('X-Webhook-Signature') ?? ''
  const expected = crypto.createHmac('sha256', secret).update(req.body).digest('hex')
  const valid = received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))
  if (!valid) return res.status(401).end()

  const payload = JSON.parse(req.body.toString('utf8'))
  res.status(200).end()
  // puis traitez payload.event et payload.data.uuid
})

Python (Flask) :

import hashlib, hmac, os
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["SWIMLANE_WEBHOOK_SECRET"].encode()

@app.post("/swimlane")
def swimlane():
    raw = request.get_data()
    expected = hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
    received = request.headers.get("X-Webhook-Signature", "")
    if not hmac.compare_digest(expected, received):
        abort(401)
    payload = request.get_json()
    return "", 200

Bon à savoir

  • Il n'y a pas d'identifiant de livraison. Pour ignorer les doublons, combinez event, data.uuid et l'état de votre propre traitement.
  • L'ordre d'arrivée n'est pas garanti en cas de nouvelle tentative : un contact.updated retenté peut arriver après un plus récent. Si l'ordre compte, relisez le contact via l'API.
  • Si vous perdez la clé, modifiez le webhook et cochez Régénérer la clé secrète. Mettez à jour votre récepteur aussitôt : les requêtes sont alors signées avec la nouvelle clé.

Cet article vous a-t-il ete utile ?