Enviar clientes potenciales con Webhooks
Este es un artículo traducido automáticamente del inglés. Ver artículo original
Spreadly envía una solicitud HTTP POST con un cuerpo JSON que contiene un único objeto de cliente potencial cada vez que se crea o actualiza uno. La autenticación utiliza una firma HMAC SHA-256 sobre el cuerpo de la solicitud en bruto.
Destinos típicos:
- Plataformas de automatización (Zapier, Make, n8n)
- Sin servidor (AWS Lambda, Vercel, Cloudflare)
- APIs personalizadas/middleware
Configuración
1) En tu panel de control de Spreadly, ve a: Clientes potenciales → Integraciones con CRM → Webhook
2) Configura:
- URL del Webhook: Tu endpoint accesible públicamente
- Clave Secreta: Copia y guarda de forma segura (por ejemplo,
SPREADLY_WEBHOOK_SECRET)
3) Guarda tu configuración
4) Enviar Prueba: Usa el botón «Probar webhook» para verificar tu endpoint
Una vez activado, Spreadly enviará el objeto de cliente potencial mediante POST al crearse o actualizarse.
Autenticación
Basada en firma (HMAC SHA-256, obligatorio):
- Encabezado:
X-Spreadly-Signature - Contenido: Firma HMAC SHA-256 del cuerpo de la solicitud en bruto, calculada con tu Clave Secreta del Webhook
Reglas de verificación:
- Calcula HMAC SHA-256 con
SPREADLY_WEBHOOK_SECRET - Usa los bytes del cuerpo de la solicitud en bruto (antes de cualquier análisis JSON)
- Compara exactamente con el encabezado
X-Spreadly-Signature
Ejemplo de verificación (Next.js/Node)
/// /pages/api/spreadly-webhook.js
import crypto from 'crypto';
export const config = {
api: { bodyParser: false }, /// importante: usa el cuerpo en bruto para 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' });
}
/// El cuerpo es un único objeto de cliente potencial
const webhook = JSON.parse(rawBody);
/// Opcional: manejar cargas de prueba (si está activado)
if (webhook.data.is_test === true) {
return res.status(200).json({ message: 'Test webhook received successfully' });
}
/// Actualizar o insertar por 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);
});
}
Estructura de la carga útil: Cliente potencial (Objeto plano)
Ejemplo de cuerpo de la solicitud:
{
"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"
}
}
Notas sobre los campos:
- id: ID interno del cliente potencial (estable en las actualizaciones; úsalo para actualizar o insertar)
- given_name, family_name, job_title, department, company: campos de perfil
- note: texto libre opcional
- emails[], phones[], websites[], addresses[]: matrices de elementos relacionados
- custom_fields[]: extensibilidad para tu esquema
- event_id: referencia opcional de evento/captura
- latitude, longitude: geolocalización opcional
- locale: código de idioma/región
- source: origen (por ejemplo, spreadly_card, scanner_app)
- campos de enriquecimiento: enriched_at, is_ai_enrichment_completed
- external_*: enlaces a sistemas externos si es aplicable
- created_at, updated_at: marcas de tiempo en ISO 8601
Banderas opcionales (si están activadas en tu espacio de trabajo):
- is_test: true puede incluirse en la raíz para marcar cargas de prueba
Pruebas
- Ve a Clientes potenciales → Integraciones con CRM → Webhook y haz clic en «Probar Webhook»
- Espera HTTP 200 si se maneja correctamente
- Si está activado, las cargas de prueba incluyen
is_test: trueen el mismo objeto plano de cliente potencial - Trata
is_test: truecomo no persistente y simplemente devuelve 200
Solución de problemas
El webhook no se activa:
- Asegúrate de que el endpoint sea accesible públicamente
- Devuelve HTTP 200 al tener éxito
- Verifica que el webhook esté activado en Clientes potenciales → Integraciones con CRM → Webhook
- Usa «Probar Webhook» para validar
La prueba de conexión falló:
- Acepta POST
- Verifica la URL y la disponibilidad
- Confirma que el servidor/función esté en ejecución
- Revisa los registros del servidor
Actualizado el: 18/06/2026
¡Gracias!