Webhooks
Cada vez que un huésped se conecta al WiFi de un sitio, Bitwifi puede avisar a tu sistema (CRM, email marketing, automatización) con un POST en JSON a la URL que configures. También puede avisarte cuando un huésped se elimina, para que lo borres o anonimices de tu lado.
El envío ocurre en segundo plano y nunca retrasa el login del huésped. Si tu endpoint está caído o lento, el huésped navega igual.
Configuración
Se configura por cuenta en el panel: Marketing → Integraciones. Puedes tener varias integraciones en la misma cuenta.
| Campo | Descripción |
|---|---|
| URL del webhook | Tu endpoint. Debe ser https, con un dominio público (no se aceptan IPs privadas, localhost ni usuario y clave dentro de la URL). |
| Formato | bitwifi (recomendado), legacy (compatible con integraciones antiguas) o ecus (eCus / Aliatec). |
| Token | Opcional. Se envía en un header de autenticación. Se guarda cifrado. |
| Nombre del header | Por defecto Authorization. Cámbialo si tu endpoint espera, por ejemplo, X-API-Key. |
| Prefijo | Por defecto Bearer. Déjalo vacío para enviar el token sin prefijo. |
| Cuándo enviar | new_user (por defecto): solo la primera visita de esa persona. every_login: en cada conexión. |
| Enviar eliminaciones | Si está activo, recibes un evento cuando un administrador elimina huéspedes. |
| Datos sensibles | Solo formato bitwifi. Agrega documento y etnia. Actívalo solo si el receptor los necesita y tienes base legal para compartirlos. |
Identificador del huésped
El campo id del huésped es un UUID estable dentro de la cuenta: la misma persona (el mismo dispositivo) conserva el mismo id en todas sus visitas y en todos los sitios de la cuenta. Úsalo como clave para no duplicar contactos.
Eventos
- Formato bitwifi
- Formato legacy
Todos los eventos comparten la misma envoltura:
| Campo | Descripción |
|---|---|
format | Siempre "bitwifi". |
version | Entero. Solo sube con cambios que rompan a los receptores; los campos nuevos no cambian la versión. Hoy es 1. |
id | ID único del evento (evt_…). Úsalo para descartar duplicados. |
type | guest.connected o guest.erased. |
occurred_at | Fecha del evento, ISO 8601 en UTC. |
guest.connected
El huésped se conectó al WiFi de un sitio.
{
"format": "bitwifi",
"version": 1,
"id": "evt_4f1c2a9be7d84b0c9a51e3f2d6c7b8a0",
"type": "guest.connected",
"occurred_at": "2026-11-13T15:04:05.000Z",
"account": { "id": "acc_123", "name": "Café Andino" },
"site": {
"id": "cafe-norte",
"name": "Café Andino — Sucursal Norte",
"timezone": "America/Bogota",
"location": {
"country": "Colombia",
"state": "Cundinamarca",
"city": "Bogotá",
"address": "Av. Principal 456",
"postal_code": null
}
},
"access_point": { "mac": "aa:bb:cc:dd:ee:ff", "name": "AP Terraza" },
"guest": {
"id": "b3f2a9c1-6e4d-4a12-8f30-1d9c7e4b5a6f",
"first_name": "María",
"last_name": "Torres",
"phone": "+573001234567",
"birth_date": null,
"gender": null,
"language": "es",
"email": {
"address": "maria.torres@example.com",
"status": "valid",
"opt_out": false,
"engagement": { "sent": 4, "opened": 2, "clicked": 1, "last_sent_at": "2026-11-01T12:00:00Z" }
}
},
"device": { "type": "mobile", "os": "iOS", "os_version": "18.1", "brand": "Apple" },
"visit": {
"is_first": false,
"number": 5,
"first_at": "2025-11-02T14:20:11Z",
"session": { "granted_minutes": 60, "expires_at": "2026-11-13T16:04:05Z" }
},
"consent": {
"terms_accepted_at": "2025-11-02T14:20:11Z",
"terms_text": "Acepto recibir comunicaciones de marketing de este establecimiento."
},
"answers": [
{ "question": "¿Cómo nos conociste?", "answer": "Redes sociales" }
]
}
| Campo | Notas |
|---|---|
access_point | null si el equipo no informa la MAC del AP. |
guest.email.status | valid si Bitwifi ya entregó un correo a esa dirección, invalid si rebotó, unknown si aún no se sabe. |
guest.email.opt_out | true si el huésped se dio de baja de los correos. |
guest.email.engagement | Actividad de los correos que Bitwifi le envió. Puede no venir. |
guest.sensitive | Solo si activaste Datos sensibles: document (type, number) y ethnicity. |
visit.number | Número de visitas de ese huésped en el sitio, contando esta. |
answers | Pregunta prelogin del sitio, si existe. Arreglo vacío si no. |
Los campos que el huésped no completó llegan en null.
guest.erased
Un administrador eliminó al huésped. Si la misma persona está en varios sitios de la cuenta, el evento llega cuando se elimina la última.
{
"format": "bitwifi",
"version": 1,
"id": "evt_9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d",
"type": "guest.erased",
"occurred_at": "2026-11-13T15:10:00.000Z",
"account": { "id": "acc_123" },
"site": { "id": "cafe-norte" },
"guest": {
"id": "b3f2a9c1-6e4d-4a12-8f30-1d9c7e4b5a6f",
"email": { "address": "maria.torres@example.com" }
}
}
Formato anterior. Las integraciones conectadas antes de que existiera el formato bitwifi siguen recibiéndolo hasta que lo cambies en el panel. Para integraciones nuevas, usa bitwifi.
user.login
{
"event": "user.login",
"data": {
"user": {
"id": "b3f2a9c1-6e4d-4a12-8f30-1d9c7e4b5a6f",
"email": "maria.torres@example.com",
"email-confirmed": true,
"first-name": "María",
"last-name": "Torres",
"phone": "+573001234567",
"phone-confirmed": false,
"birth-date": null,
"gender": null,
"is-subscribed": true
},
"venue": {
"id": "cafe-norte",
"name": "Café Andino — Sucursal Norte",
"address": "Av. Principal 456",
"city": "Bogotá",
"country": "Colombia",
"created-at": "2024-02-10 09:00:00"
},
"user-venue-history": {
"visits-count": 5,
"first-visit-on": "2025-11-02 14:20:11",
"last-visit-on": "2026-11-13 15:04:05"
},
"user-venue-agreements": {
"accepted-date": "2025-11-02 14:20:11",
"texts": ["Acepto recibir comunicaciones de marketing de este establecimiento."]
},
"question-answers": []
}
}
email-confirmedestruesolo si Bitwifi ya entregó un correo a esa dirección.phone-confirmedsiempre esfalse: Bitwifi no verifica teléfonos.is-subscribedsiempre estrue.- Las fechas van en UTC con formato
YYYY-MM-DD HH:MM:SS.
user.deleted
{
"event": "user.deleted",
"data": {
"user": { "id": "b3f2a9c1-6e4d-4a12-8f30-1d9c7e4b5a6f", "email": "maria.torres@example.com" },
"venue": { "id": "cafe-norte" }
}
}
Entrega
| Aspecto | Comportamiento |
|---|---|
| Método | POST con Content-Type: application/json. |
| Tiempo máximo | 10 segundos. |
| Redirecciones | No se siguen. Usa la URL final de tu endpoint. |
| Reintentos | Ninguno. Un error de red o una respuesta 5xx se descarta. |
| Orden | No garantizado entre eventos. |
| Autenticación | El header con tu token, si lo configuraste. No hay firma HMAC del cuerpo. |
Si tu integración necesita reintentos o verificación por firma, escríbenos: es una mejora planeada.
Buenas prácticas
- Responde
200apenas recibas el evento y procésalo después en tu lado. - Usa
guest.id(odata.user.iden legacy) como clave del contacto, y eliddel evento para descartar duplicados. - Con
every_login, trata cada evento como una actualización del contacto, no como un contacto nuevo. - Ignora los campos que no conozcas: el payload puede ganar campos nuevos sin aviso. Nunca quitamos campos sin subir la versión.
- Valida el header de autenticación en cada request.