Recepción de Leads Multi-Contrato desde el Sitio Web
Este documento describe un único contrato API a través del cual el backend del sitio envía datos de lead, los detalles de contacto del visitante y las métricas UTM de marketing a ReactiveCRM en una sola solicitud.
1. Propósito
El integrador del sitio no necesita crear por separado el contacto, teléfono, correo electrónico, lead y evento de marketing. Una sola solicitud debe crear atómicamente:
- el contacto;
- el teléfono principal del contacto;
- el correo electrónico principal del contacto, si se proporciona;
- el lead;
- el vínculo entre el contacto y el lead;
- el evento de marketing y la atribución UTM.
Si falla algún paso, toda la solicitud se revierte y los datos parcialmente creados no se guardan.
2. Endpoint
POST /api/site/leads
Authorization: Bearer <CRM_ACCESS_TOKEN>
Content-Type: application/json
El token debe pasarse únicamente desde el backend del sitio o una función serverless. Nunca coloque el token de CRM en JavaScript del navegador.
El inquilino se determina a partir del token de autorización. Todos los UUID proporcionados se validan dentro de este inquilino.
3. Contrato JSON completo
{
"externalId": "site-form-01J7K9A2M4Y5T6",
"lead": {
"leadSourceId": "7b33b18f-55bd-48a5-a8f0-e450a56dde47",
"quality": "WARM",
"message": "Me gustaría una asesoría sobre la implementación de CRM"
},
"contact": {
"firstName": "Ivan",
"lastName": "Ivanov",
"phone": "+79991234567",
"email": "ivan@example.com"
},
"marketing": {
"occurredAt": "2026-09-06T18:00:00Z",
"visitorId": "visitor-8b7f",
"sessionId": "session-31ac",
"source": "website",
"channel": "paid",
"utmSource": "google",
"utmMedium": "cpc",
"utmCampaign": "summer-consulting",
"utmContent": "banner-a",
"utmTerm": "crm consultation",
"gclid": "EAIaIQobChMI-example",
"fbclid": null,
"landingUrl": "https://example.com/consultation",
"referrer": "https://www.google.com/"
}
}
4. Campos de la solicitud
4.1. Campos raíz
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
externalId | cadena | recomendado | ID único estable del envío del formulario para idempotencia |
lead | objeto | sí | Datos del lead que se está creando |
contact | objeto | sí | Datos de la persona de contacto |
marketing | objeto | no | Métricas UTM y datos técnicos de la visita de marketing |
externalId se genera antes del primer intento de envío. Al repetir la solicitud después de un tiempo de espera o error de red, use el mismo valor.
4.2. El objeto lead
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
leadSourceId | uuid | sí | ID de la fuente de leads en el inquilino actual |
quality | cadena | no | Calificación inicial: HOT, WARM, COOL o COLD |
message | cadena | no | Mensaje del visitante o comentario sobre el envío |
Si el sitio no realiza una precalificación, es mejor no enviar el campo quality.
4.3. El objeto contact
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
firstName | cadena | sí | Nombre de la persona de contacto |
lastName | cadena | no | Apellido de la persona de contacto |
phone | cadena | sí | Teléfono principal; se recomienda el formato E.164 |
email | cadena(email) | no | Correo electrónico principal de la persona de contacto |
Los datos mínimos necesarios para procesar un envío son firstName y phone.
4.4. El objeto marketing
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
occurredAt | fecha-hora | no | Hora del envío del formulario; se usa la hora de CRM si está ausente |
visitorId | cadena | no | ID de visitante anónimo |
sessionId | cadena | no | ID de sesión del sitio |
source | cadena | no | Fuente del evento, por defecto website |
channel | cadena | no | Canal: ej. paid, organic, social, email, direct |
utmSource | cadena | no | Valor de utm_source |
utmMedium | cadena | no | Valor de utm_medium |
utmCampaign | cadena | no | Valor de utm_campaign |
utmContent | cadena | no | Valor de utm_content |
utmTerm | cadena | no | Valor de utm_term |
gclid | cadena | no | ID de clic de Google |
fbclid | cadena | no | ID de clic de Meta/Facebook |
landingUrl | cadena | no | URL de la página de destino |
referrer | cadena | no | URL de la página anterior |
Los datos personales no deben duplicarse en el objeto marketing. El nombre, teléfono, correo electrónico y el texto del mensaje se pasan únicamente en contact y lead.
5. Solicitud mínima
{
"externalId": "site-form-550e8400-e29b-41d4-a716-446655440000",
"lead": {
"leadSourceId": "7b33b18f-55bd-48a5-a8f0-e450a56dde47"
},
"contact": {
"firstName": "Ivan",
"phone": "+79991234567"
}
}
6. Respuesta exitosa
Primera solicitud — 201 Created
{
"leadId": "22222222-2222-2222-2222-222222222222",
"contactId": "33333333-3333-3333-3333-333333333333",
"phoneId": "44444444-4444-4444-4444-444444444444",
"emailId": "55555555-5555-5555-5555-555555555555",
"marketingEventId": "66666666-6666-6666-6666-666666666666",
"duplicate": false,
"createdAt": "2026-09-06T18:00:01Z"
}
Si no se proporcionan datos de correo electrónico o marketing, los ID correspondientes se devuelven como null.
Repetición con el mismo externalId — 200 OK
{
"leadId": "22222222-2222-2222-2222-222222222222",
"contactId": "33333333-3333-3333-3333-333333333333",
"phoneId": "44444444-4444-4444-4444-444444444444",
"emailId": "55555555-5555-5555-5555-555555555555",
"marketingEventId": "66666666-6666-6666-6666-666666666666",
"duplicate": true,
"createdAt": "2026-09-06T18:00:01Z"
}
Una solicitud repetida no debe crear un segundo lead o contacto.
7. Ejemplo con cURL
curl -X POST "$CRM_BASE_URL/api/site/leads" \
-H "Authorization: Bearer $CRM_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"externalId": "site-form-01J7K9A2M4Y5T6",
"lead": {
"leadSourceId": "7b33b18f-55bd-48a5-a8f0-e450a56dde47",
"quality": "WARM",
"message": "Llámeme de vuelta"
},
"contact": {
"firstName": "Ivan",
"lastName": "Ivanov",
"phone": "+79991234567",
"email": "ivan@example.com"
},
"marketing": {
"utmSource": "google",
"utmMedium": "cpc",
"utmCampaign": "crm-demo",
"landingUrl": "https://example.com/demo"
}
}'
8. Interfaces TypeScript
type LeadQuality = 'HOT' | 'WARM' | 'COOL' | 'COLD';
interface SiteLeadRequest {
externalId?: string;
lead: {
leadSourceId: string;
quality?: LeadQuality;
message?: string;
};
contact: {
firstName: string;
lastName?: string;
phone: string;
email?: string;
};
marketing?: {
occurredAt?: string;
visitorId?: string;
sessionId?: string;
source?: string;
channel?: string;
utmSource?: string;
utmMedium?: string;
utmCampaign?: string;
utmContent?: string;
utmTerm?: string;
gclid?: string;
fbclid?: string;
landingUrl?: string;
referrer?: string;
};
}
interface SiteLeadResponse {
leadId: string;
contactId: string;
phoneId: string;
emailId: string | null;
marketingEventId: string | null;
duplicate: boolean;
createdAt: string;
}
9. Ejemplo del lado del servidor
export async function sendLeadToCrm(payload: SiteLeadRequest): Promise<SiteLeadResponse> {
const response = await fetch(`${process.env.CRM_BASE_URL}/api/site/leads`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CRM_ACCESS_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
if (!response.ok) {
const body = await response.text();
throw new Error(`ReactiveCRM returned ${response.status}: ${body}`);
}
return response.json() as Promise<SiteLeadResponse>;
}
10. Errores
| HTTP | Causa | Acción del integrador |
|---|---|---|
400 | Error de formato, campo requerido faltante, correo/teléfono/quality inválido | Corrija los datos; no reintente automáticamente sin cambios |
401 | Token faltante o inválido | Actualice el token de integración |
403 | Sin acceso al inquilino o a la operación | Verifique el usuario de integración y el rol |
404 | leadSourceId no encontrado en el inquilino actual | Actualice el ID de fuente en la configuración del sitio |
409 | externalId ya está vinculado a una solicitud incompatible | Verifique la generación de ID y el registro de integración |
5xx | Error transitorio de CRM | Reintente la misma solicitud con el mismo externalId |
Ejemplo de error de validación:
{
"status": 400,
"error": "Bad Request",
"message": "contact.phone must not be blank",
"path": "/api/site/leads"
}
11. Idempotencia
externalIddebe ser único dentro del inquilino y la fuentewebsite.- Genere
externalIdantes de la primera solicitud y guárdelo en el envío del sitio. - En caso de tiempo de espera, reintente la solicitud con el mismo cuerpo y el mismo
externalId. - No cree un nuevo
externalIdal reintentar un mismo envío. - Si
duplicate: true, el envío ya ha sido procesado y se considera entregado con éxito.
12. Mini-guía para el desarrollador del sitio
- En la primera visita, almacene las etiquetas UTM,
gclid,fbclid, la URL de destino y el referente. - Cuando se envíe el formulario, genere un
externalIdestable. - Envíe el formulario al backend del sitio.
- El backend añade el token de CRM y llama a
POST /api/site/leads. - En
201o200conduplicate: true, considere el envío entregado. - En caso de tiempo de espera o
5xx, reintente la solicitud con el mismoexternalId. - Nunca envíe el token de CRM directamente desde el navegador.
13. Lo que obtiene el gestor de CRM
Después de una solicitud exitosa, el CRM contiene un lead que:
- tiene la fuente
leadSourceIdestablecida; - conserva el mensaje del visitante;
- tiene la calificación de calidad inicial, si el sitio la proporcionó;
- está vinculado a un contacto con nombre y teléfono principal;
- está vinculado a un correo electrónico principal, si se proporcionó;
- conserva las métricas UTM y los identificadores publicitarios para análisis.
Este conjunto es suficiente para que el gestor vea el envío, llame al contacto y califique el lead.
Reactive CRM