🔔 Webhooks - Zeus Colecta API

Documentación completa para integrar webhooks en tu plataforma. Recibe notificaciones en tiempo real cuando el estado de una visita cambia.

¿Qué son webhooks?
Los webhooks son notificaciones HTTP en tiempo real que enviamos a tu servidor cuando algo importante sucede en Zeus Colecta. En lugar de consultar constantemente (polling), nosotros te notificamos automáticamente.

Introducción

Los webhooks son la forma más eficiente de integrar Zeus Colecta en tu plataforma.

¿Por Qué Usar Webhooks?

Ventaja Descripción
⚡ Tiempo real Actualizaciones en menos de 1 segundo
💰 Eficiente No necesitas polling constante
🔒 Seguro Autenticación con Bearer token JWT
📊 Completo Incluye toda la información de la visita
🔄 Confiable Reintentos automáticos en caso de error

Estados que Generan Webhooks

Solo dos cambios de estado disparan webhooks:

✅ completada
Visita entregada exitosamente
❌ falso_flete
No se pudo entregar (cliente ausente, rechazó, etc.)

⚡ Configuración Rápida (5 pasos)

Tendrás webhooks funcionando en 5 minutos siguiendo estos pasos.

Paso 1️⃣ : Endpoint HTTPS

Tu servidor debe tener un endpoint HTTPS que acepte POST:

https://tu-api.com/webhooks/estado-cambios

Paso 2️⃣ : Validar Token

Recibirás un header de autenticación:

Authorization: Bearer {JWT_TOKEN}

Paso 3️⃣ : Procesar JSON

El payload incluye toda la información de la visita

Paso 4️⃣ : Responder con 200

Confirma la recepción con HTTP 200-204

Paso 5️⃣ : Registrar URL

Comunícanos tu endpoint para habilitarlo

Ejemplo Mínimo (Python)

from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_TOKEN = "tu_token_aqui"

@app.route('/webhooks/estado-cambios', methods=['POST'])
def handle_webhook():
    # Validar token
    token = request.headers.get('Authorization', '').replace('Bearer ', '')
    if token != WEBHOOK_TOKEN:
        return jsonify({"error": "No autorizado"}), 401

    # Procesar
    data = request.get_json()
    visita = data['data']

    if visita['estado_nuevo'] == 'completada':
        print(f"✅ Visita {visita['visita_id']} completada")

    return jsonify({"status": "ok"}), 200

if __name__ == '__main__':
    app.run(port=5000)

Testing Rápido

Opción 1: Webhook.site (Recomendado - Sin código)

  1. Ve a https://webhook.site
  2. Obtén tu URL pública
  3. Comunícanos la URL
  4. Verifica en tiempo real

Opción 2: ngrok (Para servidor local)

ngrok http 5000
# Compartir: https://abc123.ngrok.io/webhooks/estado-cambios

📋 Especificación Técnica

Headers HTTP

Cada webhook se envía con estos headers:

POST /webhooks/estado-cambios HTTP/1.1
Host: tu-api.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Payload JSON Completo

{
  "event": "visita_estado_cambio",
  "timestamp": "2026-06-01T14:30:45.123456",
  "data": {
    "visita_id": 12345,
    "tracking_id": "REF-001-2026",
    "titulo": "Entrega Cliente ABC",
    "direccion": "Calle Principal 123",
    "estado_anterior": "pendiente",
    "estado_nuevo": "completada",
    "numero_seguimientos": ["330005000000000431"],
    "conductor": {
      "id": 5,
      "nombre": "Juan Pérez García"
    },
    "ruta": {
      "id": 1,
      "nombre": "Ruta Centro Lima"
    },
    "contacto": {
      "nombre": "María García López",
      "telefono": "+51 987 654 321",
      "email": "maria@example.com"
    },
    "ubicacion": {
      "latitud": "-12.0456",
      "longitud": "-77.0456"
    },
    "check_in": {
      "fecha_hora": "2026-06-01T10:30:00",
      "latitud": "-12.0456",
      "longitud": "-77.0456"
    },
    "check_out": {
      "fecha_hora": "2026-06-01T10:45:00",
      "latitud": "-12.0457",
      "longitud": "-77.0457"
    },
    "fotos": {
      "dni": "https://storage.example.com/dni-001.jpg",
      "evidencia": "https://storage.example.com/evidencia-001.jpg"
    }
  }
}

Códigos de Respuesta HTTP

Código Significado Acción
200 OK - Procesado Registramos éxito inmediatamente
201 Created Registramos éxito inmediatamente
204 No Content Registramos éxito inmediatamente
4xx Error cliente No se reintenta
5xx Error servidor Se reintenta hasta 3 veces

Reintentos y Backoff

Si tu servidor responde con error 5xx:

  1. Primer intento: Inmediato
  2. Segundo intento: Después de ~5 segundos
  3. Tercer intento: Después de ~25 segundos
  4. Abandono: Si todos fallan, se registra el error

Deduplicación (Idempotencia)

Para evitar procesar el mismo webhook dos veces, usa esta clave única:

unique_key = f"{visita_id}_{timestamp}"

# En tu base de datos
CREATE UNIQUE INDEX idx_visita_timestamp
  ON webhook_events(visita_id, timestamp);

💻 Ejemplos de Código

Python (Flask)

from flask import Flask, request, jsonify

app = Flask(__name__)
WEBHOOK_TOKEN = "tu_token_aqui"

@app.route('/webhooks/estado-cambios', methods=['POST'])
def handle_webhook():
    token = request.headers.get('Authorization', '').replace('Bearer ', '')
    if token != WEBHOOK_TOKEN:
        return jsonify({"error": "Token inválido"}), 401

    data = request.get_json()
    visita = data['data']

    if visita['estado_nuevo'] == 'completada':
        print(f"✅ Visita {visita['visita_id']} completada")
        actualizar_en_bd(visita)

    return jsonify({"status": "ok"}), 200

Node.js (Express)

const express = require('express');
const app = express();
app.use(express.json());

const WEBHOOK_TOKEN = "tu_token_aqui";

app.post('/webhooks/estado-cambios', (req, res) => {
    const token = (req.headers.authorization || '').replace('Bearer ', '');
    if (token !== WEBHOOK_TOKEN) {
        return res.status(401).json({ error: 'Token inválido' });
    }

    const { data } = req.body;
    console.log(`Visita ${data.visita_id}: ${data.estado_nuevo}`);

    res.status(200).json({ status: 'ok' });
});

app.listen(3000);

PHP

<?php
$WEBHOOK_TOKEN = "tu_token_aqui";

$token = str_replace('Bearer ', '', $_SERVER['HTTP_AUTHORIZATION'] ?? '');
if ($token !== $WEBHOOK_TOKEN) {
    http_response_code(401);
    echo json_encode(['error' => 'Token inválido']);
    exit;
}

$data = json_decode(file_get_contents('php://input'), true);
$visita = $data['data'];

if ($visita['estado_nuevo'] === 'completada') {
    error_log("Visita {$visita['visita_id']} completada");
}

http_response_code(200);
echo json_encode(['status' => 'ok']);
?>

Go

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "strings"
)

const WEBHOOK_TOKEN = "tu_token_aqui"

func handleWebhook(w http.ResponseWriter, r *http.Request) {
    token := strings.Replace(r.Header.Get("Authorization"), "Bearer ", "", 1)
    if token != WEBHOOK_TOKEN {
        w.WriteHeader(http.StatusUnauthorized)
        json.NewEncoder(w).Encode(map[string]string{"error": "Token inválido"})
        return
    }

    var payload map[string]interface{}
    json.NewDecoder(r.Body).Decode(&payload)

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}

func main() {
    http.HandleFunc("/webhooks/estado-cambios", handleWebhook)
    log.Println("Escuchando en :5000")
    http.ListenAndServe(":5000", nil)
}

🔧 Troubleshooting & FAQs

Problema: "Rechazo mi webhook"

Soluciones:
  1. Verifica que tu endpoint sea HTTPS (no HTTP)
  2. Asegúrate de responder con 200-204
  3. Valida que el token sea correcto
  4. Verifica que procese en menos de 15 segundos

Problema: "No recibo webhooks"

Soluciones:
  1. Verifica que tu URL sea pública e HTTPS
  2. Asegúrate de que el firewall no bloquee
  3. Revisa los logs en nuestro dashboard
  4. Prueba con Webhook.site

¿Qué pasa si mi servidor está caído?

Reintentamos automáticamente 3 veces con backoff exponencial. Si sigue fallando, se registra en nuestros logs.

Preguntas Frecuentes

¿Qué estados generan webhooks?

Solo completada y falso_flete. Otros estados se ignoran.

¿Cuánto tarda en llegar un webhook?

Típicamente menos de 1 segundo desde que ocurre el cambio.

¿Se garantiza la entrega?

Reintentamos 3 veces. Es tu responsabilidad implementar idempotencia.

¿Cómo evito procesar el mismo webhook 2 veces?

Usa visita_id + timestamp como clave única en tu BD.

¿El token es secreto?

Sí. No lo publiques en GitHub. Trata como una contraseña.

¿Puedo cambiar el token o URL?

Sí. Comunícate con nuestro equipo técnico para rotarlo de forma segura.

✅ Checklist de Implementación

  • Endpoint HTTPS configurado
  • Token validado en header Authorization
  • Payload JSON parseado correctamente
  • Respuesta HTTP 200-204 enviada
  • Datos guardados en base de datos
  • Idempotencia implementada (visita_id + timestamp)
  • Logs configurados para auditoría
  • Probado en ambiente de desarrollo
  • URL comunicada a nuestro equipo técnico

📞 Soporte & Contacto

¿Preguntas o problemas? Contáctanos:

📱 WhatsApp
+51 987 654 321