AzterDocs

Guías

Conectar un CRM personalizado a Azter

Conecta tu CRM, formulario o landing page para que cada lead llegue a Azter automáticamente: se crea el contacto, la oportunidad comercial y —si hay consentimiento— se envía el primer mensaje por WhatsApp.

Cómo funciona

Azter expone un endpoint HTTP autenticado con firma HMAC. Tu sistema envía un POST por cada lead nuevo; Azter verifica la firma, registra el evento y ejecuta la activación. No necesitas SDK: cualquier lenguaje con HTTP y HMAC-SHA256 sirve.

  • Cada conector pertenece a tu empresa (workspace) y tiene su propio secret.
  • Las peticiones se firman con HMAC-SHA256 y expiran a los 5 minutos (protección contra replay).
  • Los reenvíos del mismo lead son idempotentes: no se duplican contactos ni mensajes.
  • El consentimiento de WhatsApp viaja en el payload y se audita en un registro inmutable.

1. Crear el conector

En Azter, un owner o admin de tu empresa va a Integraciones y crea un conector. Elige un nombre visible (ej. "CRM Properly") y un identificador de sistema sin espacios (ej. properly_crm). Azter genera el secret en el servidor y lo muestra una sola vez: guárdalo en un gestor de secretos, nunca en el código. Nadie puede volver a verlo después — solo reemplazarlo rotándolo.

La pantalla del conector te entrega dos datos que tu desarrollador necesita: el endpoint (POST /functions/v1/lead-intake) y el ID del conector (un UUID). Si el secret se compromete, un owner/admin puede rotarlo desde la misma pantalla; el anterior deja de funcionar de inmediato y el nuevo se muestra una sola vez.

2. Enviar un lead

Cada lead nuevo se envía como un POST con tres headers de autenticación (el endpoint exacto aparece en la pantalla del conector en Integraciones):

Petición
1curl -X POST <endpoint-de-integraciones> \
2 -H "Content-Type: application/json" \
3 -H "X-Azter-Connector-Id: <connector_id>" \
4 -H "X-Azter-Timestamp: <unix_ms>" \
5 -H "X-Azter-Signature: <hmac_hex>" \
6 -d @lead.json
  • X-Azter-Connector-Id: el UUID del conector.
  • X-Azter-Timestamp: milisegundos Unix actuales (máximo 5 minutos de antigüedad).
  • X-Azter-Signature: HMAC-SHA256 del secret sobre timestamp + "." + cuerpo crudo.

3. Firmar la petición

La firma se calcula sobre el cuerpo crudo (raw body) tal como se transmite — no sobre el objeto parseado. Primero concatena timestamp + "." + body, firma con tu secret y envía el resultado en hexadecimal:

javascript
1import { createHmac } from "node:crypto";
2
3const body = JSON.stringify(payload);
4const timestamp = Date.now().toString();
5
6const signature = createHmac("sha256", CONNECTOR_SECRET)
7 .update(timestamp + "." + body)
8 .digest("hex");
9
10await fetch(ENDPOINT, {
11 method: "POST",
12 headers: {
13 "Content-Type": "application/json",
14 "X-Azter-Connector-Id": CONNECTOR_ID,
15 "X-Azter-Timestamp": timestamp,
16 "X-Azter-Signature": signature,
17 },
18 body,
19});

Importante

Firma exactamente los bytes que envías. Si serializas el JSON dos veces (una para firmar y otra para enviar), la firma no va a coincidir. Reutiliza la misma string.

Payload

lead.json
1{
2 "event": "lead.created",
3 "source_lead_id": "crm-lead-8841",
4 "contact": {
5 "name": "María González",
6 "phone_e164": "+56912345678",
7 "email": "maria@ejemplo.cl"
8 },
9 "context": {
10 "catalog_item_id": "torre-norte-2d",
11 "source": "meta_ads",
12 "campaign_id": "1202••••",
13 "landing_page": "https://tusitio.cl/torre-norte"
14 },
15 "qualification": {
16 "monthly_income": 2800000
17 },
18 "whatsapp_consent": {
19 "granted": true,
20 "timestamp": "2026-08-30T14:03:00Z",
21 "source": "crm_form",
22 "purpose": "lead_followup"
23 },
24 "metadata": { "notas": "Consultó por 2 dormitorios" }
25}

Campos

  • event: hoy solo se acepta "lead.created".
  • source_lead_id (obligatorio): identificador único del lead en tu sistema. Azter lo usa para idempotencia.
  • contact.phone_e164 (obligatorio): teléfono en formato E.164 con +.
  • contact.name / contact.email (opcionales): se usan para identidad y enriquecimiento.
  • context (opcional): atribución — de dónde vino el lead (campaña, anuncio, landing, producto).
  • qualification (opcional): calificación ya conocida por el CRM. Solo campos definidos por el vertical del workspace (inmobiliario: monthly_income en CLP). Currency/number exigen number finito ≥ 0 — Azter NO infiere montos desde texto ("2.800.000" o rangos se rechazan con razón explícita). Los campos válidos se registran en la oportunidad con evidencia del conector; desconocidos/inválidos se rechazan con reason, nunca en silencio.
  • whatsapp_consent (opcional pero recomendado): evidencia del consentimiento para contactar por WhatsApp.
  • metadata (opcional): texto libre para tu propio trazado (no se trata como calificación confiable).

Respuesta

200 OK
1{
2 "accepted": true,
3 "duplicate": false,
4 "lead_event_id": "••••",
5 "contact_id": "••••",
6 "opportunity_id": "••••",
7 "eligible_for_whatsapp": true,
8 "outbound": { "status": "sent", "reason": null }
9}

outbound.status indica qué pasó con el primer mensaje: sent (enviado), queued, blocked (sin consentimiento o con opt-out previo) o failed (error del proveedor). El lead queda registrado igual en todos los casos.

Idempotencia y reintentos

El par (connector_id, source_lead_id) es único. Si reenvías el mismo lead — por timeouts o reintentos — Azter responde 200 con duplicate: true y el estado previo, sin crear nada nuevo ni reenviar mensajes. Puedes reintentar con seguridad ante cualquier error de red.

Consentimiento

Azter solo envía WhatsApp si whatsapp_consent.granted es true con su timestamp. El consentimiento se persiste en un registro auditable junto al origen que lo declaró. Si el contacto tiene una revocación previa (opt-out) en Azter, el envío se bloquea aunque el payload traiga consentimiento.

Errores

401Faltan headers de autenticación, el conector no existe, la firma es inválida o el timestamp expiró (>5 min).
403El conector está pausado. Actívalo en Integraciones.
400Payload inválido: event distinto de lead.created, falta source_lead_id o contact.phone_e164.
500Error interno. Reintenta con backoff; la idempotencia evita duplicados.