Envoyer des leads avec des Webhooks
Cet article a été traduit automatiquement depuis l'anglais. View original article
Spreadly envoie une requête HTTP POST avec un corps JSON contenant un seul objet lead chaque fois qu'un lead est créé ou mis à jour. L'authentification utilise une signature HMAC SHA-256 sur le corps brut de la requête.
Destinations typiques :
- Plateformes d'automatisation (Zapier, Make, n8n)
- Serverless (AWS Lambda, Vercel, Cloudflare)
- API personnalisées/middleware
Configuration
1) Dans ton tableau de bord Spreadly, va dans : Leads → Intégrations CRM → Webhook
2) Configure :
- URL du Webhook : Ton point de terminaison accessible publiquement
- Clé secrète : Copie et stocke-la en sécurité (par exemple,
SPREADLY_WEBHOOK_SECRET)
3) Enregistre ta configuration
4) Envoyer un test : Utilise le bouton « Test webhook » pour vérifier ton point de terminaison
Une fois activé, Spreadly enverra l'objet lead par POST à la création et à la mise à jour.
Authentification
Basée sur une signature (HMAC SHA-256, obligatoire) :
- En-tête :
X-Spreadly-Signature - Contenu : Signature HMAC SHA-256 du corps brut de la requête, calculée avec ta Clé Secrète du Webhook
Règles de vérification :
- Calcule le HMAC SHA-256 avec
SPREADLY_WEBHOOK_SECRET - Utilise les octets bruts du corps de la requête (avant tout parsing JSON)
- Compare exactement avec l'en-tête
X-Spreadly-Signature
Exemple de vérification (Next.js/Node)
// /pages/api/spreadly-webhook.js
import crypto from 'crypto';
export const config = {
api: { bodyParser: false }, // important: use raw body for HMAC
};
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ message: 'Method Not Allowed' });
}
const rawBody = await getRawBody(req);
const secret = process.env.SPREADLY_WEBHOOK_SECRET;
const signature = req.headers['x-spreadly-signature'];
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
if (!signature || signature !== expectedSignature) {
return res.status(401).json({ message: 'Invalid signature' });
}
// Body is a single lead object
const webhook = JSON.parse(rawBody);
// Optional: handle test payloads (if enabled)
if (webhook.data.is_test === true) {
return res.status(200).json({ message: 'Test webhook received successfully' });
}
// Upsert by lead.id
// await upsertLead(lead);
return res.status(200).json({ message: 'Webhook received successfully' });
}
function getRawBody(req) {
return new Promise((resolve, reject) => {
const chunks = [];
req.on('data', chunk => chunks.push(chunk));
req.on('end', () => resolve(Buffer.concat(chunks)));
req.on('error', reject);
});
}
Structure de la charge utile : Lead (Objet plat)
Exemple de corps de requête :
{
"type": "lead.created",
"timestamp": "2025-10-22T19:51:54.000000Z",
"data": {
"id": 0,
"avatar_url": "https://localhost/img/assets/default_avatar.jpg",
"given_name": "John",
"family_name": "Doe",
"job_title": "Sales Manager",
"department": null,
"company": "Acme, Inc.",
"note": "This is a test note",
"emails": [
{ "id": 0, "email": "j.smith@acme.com" }
],
"phones": [
{ "id": 0, "number": "+39 58273 83572", "type": "mobile" }
],
"websites": [
{ "id": 0, "url": "https://acme.com" }
],
"addresses": [
{
"id": 0,
"line1": "Via Roma 12",
"line2": null,
"line3": null,
"city": "Roma",
"state": null,
"postal_code": null,
"country": "Italy"
}
],
"custom_fields": [],
"event_id": null,
"latitude": null,
"longitude": null,
"locale": "en",
"source": null,
"enriched_at": null,
"is_ai_enrichment_completed": true,
"external_id": null,
"external_system": null,
"external_url": null,
"created_at": "2025-10-22T19:51:54.000000Z",
"updated_at": "2025-10-22T19:51:54.000000Z"
}
}
Remarques sur les champs :
- id : ID interne du lead (stable lors des mises à jour ; à utiliser pour l'upsert)
- given_name, family_name, job_title, department, company : champs de profil
- note : texte libre optionnel
- emails[], phones[], websites[], addresses[] : tableaux d'éléments associés
- custom_fields[] : extensibilité pour ton schéma
- event_id : référence optionnelle à un événement/capture
- latitude, longitude : géolocalisation optionnelle
- locale : code de langue/région
- source : origine (par exemple, spreadly_card, scanner_app)
- champs d'enrichissement : enriched_at, is_ai_enrichment_completed
- external_* : liens vers des systèmes externes si applicable
- created_at, updated_at : horodatages ISO 8601
Drapeaux optionnels (si activés dans ton espace de travail) :
- is_test : true peut être inclus à la racine pour marquer les charges utiles de test
Test
- Va dans Leads → Intégrations CRM → Webhook et clique sur « Test Webhook »
- Attends-toi à un HTTP 200 si le traitement est réussi
- Si activé, les charges utiles de test incluent
is_test: truesur le même objet lead plat - Traite
is_test: truecomme non persistant et renvoie simplement 200
Résolution des problèmes
Le webhook ne se déclenche pas :
- Assure-toi que le point de terminaison est accessible publiquement
- Renvoie HTTP 200 en cas de succès
- Vérifie que le webhook est activé dans Leads → Intégrations CRM → Webhook
- Utilise « Test Webhook » pour valider
Échec du test de connexion :
- Accepte POST
- Vérifie l'URL et la disponibilité
- Confirme que le serveur/fonction est en cours d'exécution
- Consulte les journaux du serveur
Mis à jour le : 18/06/2026
Merci !