Con esta API tu sistema (ruteador, tienda, ERP) hace lo mismo que el panel de Xip, pero solo:
- Crea una lista de confirmación y recibe un link por persona para mandarle por WhatsApp.
- Se entera al momento de cada confirmación, por webhook, con el pin exacto, cómo entrar y el día elegido.
- Descarga la lista en CSV para cargarla en el ruteador.
- Reporta qué pasó en la puerta. Una entrega hecha cerca del pin sube el XipScore de esa dirección.
- Pide la dirección desde tu checkout y recibe la aprobación de la persona al momento.
La dirección siempre es de la persona. Tu sistema la lee solo mientras el permiso que ella dio siga vigente (hasta 7 días después del día de entrega) y cada lectura queda en la bitácora de acceso.
Autenticación
Crea una clave en Panel de negocio → Conectar tu sistema → Claves de API. La clave completa se muestra una sola vez; guárdala en tu sistema como secreto.
Cada pedido la lleva en la cabecera:
Authorization: Bearer xip_live_…Dirección base: https://api.xip.com.pa/v1.
La dirección anterior, https://xip.com.pa/api/v1, sigue funcionando igual: no hace falta cambiarla si ya la usas.
Errores
Todas las respuestas de error tienen la misma forma:
JSON
{ "error": { "code": "not_found", "message": "No existe esa lista." } }| HTTP | code | Cuándo |
|---|---|---|
| 400 | invalid_json | El cuerpo no es JSON válido |
| 401 | unauthorized | Falta la clave, no existe o está desactivada |
| 403 | no_access | No tienes permiso vigente sobre esa dirección |
| 404 | not_found | La lista, la persona o el código Xip no existen (o no son tuyos) |
| 409 | not_confirmed | Reportaste una entrega de alguien que todavía no confirmó |
| 410 | address_gone | La persona eliminó esa dirección |
| 413 | payload_too_large | El cuerpo pasa del límite |
| 422 | varios | Datos incompletos o inválidos; el mensaje dice qué falta |
Crear una lista
POST /confirmations
Una persona es un teléfono: si mandas varios pedidos con el mismo teléfono, van juntos en un solo link. Los teléfonos de 7 u 8 dígitos se toman como de Panamá (+507).
Terminal
curl -X POST https://api.xip.com.pa/v1/confirmations \
-H "Authorization: Bearer $XIP_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: ruta-2026-11-03-am" \
-d '{
"label": "Ruta martes AM",
"recipients": [
{ "name": "Ana Pérez", "phone": "6123-4567", "address": "Edif. Vista Mar, apto 12B", "order_ref": "PF-1001", "packages": 2 },
{ "name": "Luis Gómez", "phone": "+50769876543", "order_ref": "PF-1002", "notes": "Frágil" }
],
"delivery_options": ["2026-11-03", "2026-11-04"]
}'| Campo | Tipo | |
|---|---|---|
label | texto | Opcional. Nombre de la lista |
recipients[] | lista | Máximo 1 000 filas |
recipients[].name | texto | Obligatorio |
recipients[].phone | texto | Obligatorio |
recipients[].address | texto | Opcional. La dirección que tienes hoy, tal cual |
recipients[].order_ref | texto | Opcional. Tu número de pedido o guía |
recipients[].packages | número | Opcional, 1 por defecto |
recipients[].notes | texto | Opcional |
delivery_options | lista de fechas AAAA-MM-DD | Opcional. Los días que ofreces a esta lista (hora de Panamá, hasta 7, de hoy a 30 días). Si no lo mandas, Xip usa la regla fija de tu negocio (hora de corte, anticipación y días sin entrega). Si todos los días ya pasaron cuando la persona abre el link, le pide uno nuevo |
También puedes mandar el mismo CSV que subes en el panel, con Content-Type: text/csv y el nombre en ?label=.
Idempotencia. Con la cabecera Idempotency-Key, repetir el pedido devuelve la misma lista (HTTP 200 y "reused": true) en vez de crear otra. Úsala si tu sistema reintenta.
Respuesta 201:
JSON
{
"id": "5b0c…",
"reused": false,
"recipients": [
{ "id": "9e1a…", "name": "Ana Pérez", "phone": "+50761234567", "confirm_url": "https://xip.com.pa/c/Kp3…" }
],
"skipped": ["Fila 7: el teléfono \"12\" no parece válido."]
}Mándale a cada persona su confirm_url, o pide que Xip se lo mande por su WhatsApp con "send_whatsapp": true al crear la lista (hasta 200 en esa llamada) y POST /confirmations/{id}/send para el resto (o con ?retry_failed=true, para reintentar los que no llegaron). Cada persona trae whatsapp.status: sent, delivered, read o failed.
Si no usas el WhatsApp de Xip, mándale tú el confirm_url. Ahí pone su pin, cómo entrar y elige el día. No necesita cuenta.
Cada link vale 30 días. Si la persona ya le confirmó una dirección a tu negocio antes, el link le ofrece reusarla; nunca le muestra direcciones que no te haya compartido a ti. Las personas salen en el mismo orden de tu lista, y una lista admite hasta 1 000 filas.
Ver una lista
GET /confirmations/{id}
JSON
{
"id": "5b0c…",
"label": "Ruta martes AM",
"created_at": "2026-11-02T14:03:11Z",
"summary": { "total": 2, "confirmed": 1, "pending": 1 },
"recipients": [
{
"id": "9e1a…",
"name": "Ana Pérez",
"phone": "+50761234567",
"orders": [{ "order_ref": "PF-1001", "packages": 2, "notes": null }],
"original_address": "Edif. Vista Mar, apto 12B",
"status": "confirmed",
"delivery_choice": { "type": "home", "date": "2026-11-03" },
"address": {
"xip_code": "XIP-7KQ2",
"location": { "lat": 8.9824, "lng": -79.5199 },
"address_line": "Calle 50, Edif. Vista Mar",
"how_to_enter": "Torre B, piso 12. Avisar al portero.",
"notes": "Si no contesto, dejar en recepción",
"navigate_url": "https://xip.com.pa/t/4f…",
"access_expires_at": "2026-11-11T04:59:59Z"
},
"confirm_url": "https://xip.com.pa/c/Kp3…",
"sent_at": null,
"opened_at": "2026-11-02T15:20:00Z",
"confirmed_at": "2026-11-02T15:22:41Z"
}
]
}status puede ser pending, confirmed, cancelled o access_revoked (confirmó, pero luego quitó el permiso o venció). address solo viene mientras el permiso esté vigente.
navigate_url es la pantalla de llegada para el motorizado (Google Maps, Waze o Apple Maps, y cómo entrar). Vence al final del día de entrega.
Descargar el CSV
GET /confirmations/{id}/export.csv
Una fila por pedido, con estas columnas: orden, fecha_entrega, nombre, telefono, direccion, latitud, longitud, como_entrar, notas, paquetes, codigo_xip, link_llegada, estado, direccion_original. Las pendientes traen la dirección original de tu lista. Si una celda empieza con =, +, - o @ y no es un número, sale con un apóstrofo delante para que Excel no la ejecute como fórmula.
Reportar una entrega
POST /delivery-events
Terminal
curl -X POST https://api.xip.com.pa/v1/delivery-events \
-H "Authorization: Bearer $XIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipient_id": "9e1a…",
"event": "delivered",
"location": { "lat": 8.98245, "lng": -79.51987, "accuracy_m": 12 },
"operator_id": "moto-07"
}'| Campo | |
|---|---|
recipient_id o xip_code | Uno de los dos. recipient_id es el id de la persona en la lista |
event | delivered, failed_absent (no había nadie), failed_address (no se encontró) o call_needed |
location | Opcional, pero sin ubicación la entrega no verifica el pin. lat, lng y accuracy_m del GPS |
operator_id | Opcional. Tu identificador del motorizado |
notes | Opcional |
Solo puedes reportar sobre direcciones a las que tienes permiso vigente.
El link de llegada (navigate_url) sirve para llegar; no confirma la entrega ni suma al XipScore. La entrega se reporta aquí, con GPS.
Cuándo una entrega sube el XipScore. Si event es delivered, el GPS está a 150 m o menos del pin y su precisión es de 100 m o mejor, cuenta como evidencia real (+15 al XipScore, una vez por tipo de evento). Si quedó más lejos, se registra la desviación del pin y no suma.
Respuesta 201:
JSON
{
"id": "c41d…",
"event": "delivered",
"created_at": "2026-11-03T16:40:02Z",
"location_check": { "distance_m": 6, "max_distance_m": 150, "verified": true, "reason": null },
"xip_score": 65
}Pedir la dirección desde un checkout
POST /address-requests
Para tiendas y pasarelas de pago: pides la dirección de un número para una compra y la persona decide si la comparte. Si el WhatsApp de Xip está encendido, le llega el link al momento; si no, se lo mandas tú con approve_url.
Terminal
curl -X POST https://api.xip.com.pa/v1/address-requests \
-H "Authorization: Bearer $XIP_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: orden-88123" \
-d '{ "phone": "6123-4567", "name": "Ana Pérez", "order_ref": "88123" }'| Campo | |
|---|---|
phone | Obligatorio |
name | Opcional. Para saludar a la persona |
order_ref | Opcional. Tu número de orden |
delivery_options | Opcional. Si mandas fechas, la persona también elige el día |
send_whatsapp | Opcional, true por defecto. Con false, Xip no le escribe y tú le mandas el link |
Respuesta 201:
JSON
{ "id": "a09d…", "status": "pending", "approve_url": "https://xip.com.pa/c/Kp3…", "expires_in_hours": 24, "whatsapp": { "enabled": true, "sent": true } }GET /address-requests/{id} devuelve status:
pending: todavía no responde.approved: compartió su dirección; viene enaddress, igual que en las listas, durante 14 días si no hubo día de entrega.declined: decidió no compartirla.expired: pasaron 24 horas sin respuesta.revoked: la compartió y después te quitó el permiso.
Si la persona ya entró a Xip con su celular, en el link ve todas sus direcciones guardadas y solo elige una.
Webhooks
En Conectar tu sistema → Webhook pones la URL de tu sistema (https, puerto 443, en un servidor público). Xip crea un secreto (whsec_…) para firmar cada aviso. Con Enviar prueba llega un evento ping.
Cada aviso es un POST con JSON:
JSON
{
"id": "0f6a…",
"event": "confirmation.confirmed",
"created_at": "2026-11-02T15:22:42Z",
"data": {
"confirmation_id": "5b0c…",
"recipient": { "...": "la misma forma que en GET /confirmations/{id}" }
}
}| Evento | Cuándo |
|---|---|
confirmation.confirmed | Una persona confirmó su dirección y su día |
confirmation.revoked | La persona te quitó el permiso desde su link. data trae confirmation_id y recipient_id; desde ese momento la dirección ya no viene en la lista ni en el CSV |
address_request.approved | Un pedido de dirección de checkout fue aprobado. data trae address_request_id y request con la misma forma de cada persona de una lista |
address_request.declined | La persona decidió no compartir su dirección. data trae address_request_id |
address_request.revoked | La persona te quitó el permiso después de aprobar. data trae address_request_id |
ping | Prueba desde el panel |
Verifica la firma. La cabecera Xip-Signature trae t=<segundos unix>,v1=<firma>. La firma es el HMAC-SHA256, en hexadecimal, de <t>.<cuerpo tal cual llegó> con tu secreto. Rechaza el aviso si la firma no coincide o si t tiene más de 5 minutos.
JavaScript
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyXip(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const given = Buffer.from(parts.v1 ?? "", "hex");
return given.length === 32 && timingSafeEqual(given, Buffer.from(expected, "hex"));
}Responde con un 2xx en menos de 5 segundos. Si tu sistema no responde, Xip reintenta a los 1, 5 y 30 minutos, y a las 2 y 6 horas; cada aviso trae el mismo id en todos los intentos, así que puedes descartar repetidos. Los avisos y su estado salen en Últimos avisos del panel, donde también puedes reintentar a mano.
Límites
- 120 pedidos por minuto por clave de API. Si pasas el límite, la respuesta es
429concode: "rate_limited"; espera un minuto. - 1 000 filas por lista.