Guía de integración
Desde dar de alta a tu primer cliente hasta consultar sus cobros. Todo lo que aparece acá está en producción.
Qué es y qué no
Collect Pay conecta tu plataforma con terminales de pago y procesadores. Tú le dices cuánto cobrar y a quién imputárselo; nosotros hacemos que la máquina cobre eso y te devolvemos el hecho.
El dinero no pasa por acá. Cada pago se acredita directo en la cuenta que tu cliente conectó con su propio procesador. Collect Pay no lo recibe, no lo retiene y no lo reparte.
Tampoco guardamos a tus clientes finales. La referencia de un cobro y la persona que lo hizo son cadenas opacas que tú mandas: acá no hay nombres, teléfonos ni direcciones.
Tus credenciales
Al crear tu cuenta recibes tres cosas, y se muestran una sola vez:
| Qué | Para qué |
|---|---|
sk_live_… | Operar en producción. Va en la cabecera X-API-Key. |
sk_test_… | Lo mismo, contra el ambiente de prueba. El prefijo decide el ambiente. |
| Secreto de avisos | Con él firmamos lo que te mandamos. Sirve para verificar que somos nosotros. |
O acótala: al emitirla desde tu panel puedes pasarle empresas: [3, 7] y esa
key sólo podrá operar —y consultar— esas. Fuera de su alcance responde
empresa_fuera_del_alcance_de_la_key y los listados vienen filtrados. Es lo que
conviene si tienes una instalación por cliente y prefieres una key en cada una.
Si tu plataforma tiene muchos clientes
Hay dos formas de integrarse, y la diferencia no es de tamaño sino de dónde corre tu código.
Una sola instalación. Tu sistema corre en un lugar y desde ahí atiende a todos tus clientes. Te basta una cuenta: la key vive en tu backend y cada cliente es una empresa dentro de ella. Es lo que ya leíste arriba y no necesitas nada más.
Una instalación por cliente —cada uno con su dominio, su base y su API—. Acá una sola key es un problema: tendrías que desplegar la misma credencial en todas las instalaciones, y el día que una se filtre se filtran todos tus clientes con ella. Para eso existe la clave de partner.
Con ella abres una cuenta por cliente, en el momento en que ese cliente activa el cobro. Cada instalación guarda sólo sus propias credenciales. Si una se compromete, se compromete una.
curl -X POST https://collectpay.trackingtech.cl/api/v1/partner/cuentas \ -H 'X-API-Key: pk_live_…' -H 'Content-Type: application/json' \ -d '{"nombre":"Courier Austral","email":"ti@austral.cl", "empresa":{"nombre":"Courier Austral SpA","rut":"76.111.222-3"}}' → { "tenant_id": 12, "api_key": "sk_live_…", "api_key_test": "sk_test_…", "webhook_secret": "whs_…", "empresa": { "empresa_id": 31, … } }
Esas tres credenciales se muestran una sola vez. Guárdalas en la instalación de ese cliente y sigue desde ahí con los pasos 2 en adelante: los términos los acepta él, y la cuenta del procesador la conecta él.
La cuenta se cobra a quien la abrió. Cada cuenta recuerda con qué clave de partner nació, así que el consumo de tus clientes se consolida bajo tu nombre aunque sean veinte cuentas separadas. Cada una mantiene su propia tarifa, que puede no ser la misma para todos.
La clave de partner la emite quien opera Collect Pay, y se entrega una vez. No se solicita por API.
El modelo en una página
- Tu cuenta es la plataforma. Una sola, con su key.
- Empresa es cada cliente tuyo que cobra. Tiene su propia cuenta en el procesador.
- Sucursal es un local físico. Se espeja en el procesador con su dirección real.
- POD es una máquina. Una por caja.
- Sesión es una persona haciéndose responsable de un POD, por un turno o una ruta.
- Cobro es la deuda, y no tiene método.
- Intento es cómo se intenta saldarla. Ahí vive el método, y es el único lugar.
Esa última distinción es la que más conviene entender: un cobro puede tener varios intentos. El cliente intenta con una tarjeta, la rechazan, paga en efectivo. Un cobro, tres intentos, una deuda saldada.
Crear el courier
Lo haces tú con tu key, cuando tu cliente activa el servicio. Guarda el empresa_id
junto a ese cliente en tu sistema, o manda tu propio id en ref_externa.
curl -X POST https://collectpay.trackingtech.cl/api/v1/empresas \ -H 'X-API-Key: sk_live_…' -H 'Content-Type: application/json' \ -d '{"nombre":"Courier Austral SpA","rut":"76.111.222-3","ref_externa":"cliente-482"}'
Nace sin poder cobrar. Faltan los dos pasos siguientes, y los hace el courier, no tú.
Que acepte los términos
Muéstrale el texto y registra quién acepta. Queda guardado el contenido exacto de esa versión: publicar una nueva no cambia lo que ya se firmó.
{ "version": 1, "titulo": "…",
"tarifa": { "uf_por_transaccion": 0.0015, "definicion": "…" },
"contenido_md": "# Términos…" }{ "version": 1, "nombre": "Nombre de quien firma", "cargo": "Gerente" }Que conecte su procesador
Primero pregunta cómo. No todos los procesadores se conectan igual: con Mercado Pago el courier autoriza y vuelve; con SumUp puede autorizar o pegar una API key de su cuenta. En vez de que tu pantalla lo sepa de antemano, pídelo:
{ "conectadas": [
{ "unidad_recaudadora_id": 4, "procesador": "mercado_pago", "estado": "habilitada",
"cuenta": "ECOURIER", "maquinas": { "total": 3, "habilitadas": 3 } }
],
"puede_cobrar_con": ["mercado_pago"],
"opciones": [
{ "procesador": "sumup", "metodo": "autorizar", "recomendado": true,
"titulo": "Conectar con SumUp",
"ruta": "/api/v1/empresas/3/conexiones/sumup/autorizar",
"campos": [], "pasos": ["Aprieta el botón: te llevamos…"],
"costo": { "forma": "Un porcentaje de cada venta…", "advertencia": "Referencial…" } },
{ "procesador": "sumup", "metodo": "credenciales", …
"campos": [{ "nombre": "api_key", "etiqueta": "API key de la cuenta SumUp" }] }
] }Cada opción viene con su paso a paso redactado, la ruta que hay que llamar ya resuelta, y cuánto cuesta cobrar con ese procesador —como referencia, con su advertencia al lado: la tarifa la negocia el courier y la de verdad se la vamos a medir después, cobro a cobro—. Un procesador nuevo aparece en tu pantalla sin que toques una línea.
conectadas aparecen las dos, con cuántas
máquinas depende de cada una, y en opciones sólo las que faltan. Con eso tu pantalla
muestra «Mercado Pago conectado · SumUp por conectar» sin saber de ninguno de los dos, y nadie
tiene que crear una empresa aparte para probar un procesador nuevo. Para soltar una,
DELETE /api/v1/empresas/{id}/conexiones/{procesador}: deja sin cobrar sólo a
sus máquinas.Después, con la opción elegida: pide la URL de autorización y manda ahí el navegador del courier. Autoriza en la pantalla del procesador, con sus propias credenciales, y vuelve a donde tú digas.
{ "retorno": "https://tu-sistema.cl/integraciones/collectpay" }
→ { "url_autorizacion": "https://auth.mercadopago.com/authorization?…",
"expira_en": "2026-09-13T20:12:00Z" }?conexion=ok si autorizó, y con ?conexion=error&motivo=… si canceló o
el enlace venció —sin_autorizacion, enlace_vencido—. Siempre vuelve:
tu pantalla no se queda esperando un retorno que no llega.Una sola llamada para pintar tu pantalla
Si construyes tu propio panel de configuración, no compongas el estado desde cuatro endpoints.
Éste responde lo único que esa pantalla pregunta: si la empresa ya puede cobrar y, si no, qué le
falta. El falta viene redactado para mostrarlo tal cual.
{ "empresa_id": 3, "nombre": "Courier Austral SpA",
"terminos": { "version":1, "por":"…", "aceptado_en":"…" },
"conexion": { "estado":"habilitada", "cuenta":"…", "token_expira":"…" },
"pods": { "total":2, "habilitados":1 }, "sesiones_abiertas": 1,
"puede_cobrar": true, "falta": null }Sucursales y terminales
Las dos cosas las crea tu plataforma con tu key, por API, y Collect Pay las espeja en el procesador: la sucursal se convierte en un local y cada POD en una caja. No hay que entrar al panel del procesador a crearlas a mano.
La dirección tiene que ser real —el local se registra con ella— y la región debe existir: se acepta el nombre común («Región Metropolitana», «Biobío») y se traduce al que el procesador exige.
{ "nombre": "Bodega Central",
"direccion": { "calle":"Av. Siempreviva", "numero":"1111",
"comuna":"Santiago", "region":"Región Metropolitana",
"lat":-33.4429, "lon":-70.6539 } }Sobre esa sucursal se declara cada POD —una máquina, una caja— y la respuesta trae su
pod_id, su estado y el QR de esa caja. El codigo es tuyo y es
con el que vas a reconocer la máquina: letras, números, guión y guión bajo, entre 2 y 40, único
dentro de la empresa. Se guarda en mayúsculas.
{ "codigo": "CAJA1", "nombre": "Caja mostrador", "procesador": "sumup" }La cuenta la dice la máquina, no la sucursal. En la misma bodega puede haber una Point de
Mercado Pago y un Solo de SumUp: procesador amarra el POD a esa cuenta y, de ahí en
adelante, cada cobro de esa máquina sale por ella sin que nadie lo repita. Con una sola cuenta
conectada se omite. Con dos o más, un intento que no venga de una máquina —un QR, un
link— tiene que decir por cuál sale; si no, responde procesador_ambiguo en vez de
adivinar, porque adivinar es cobrar por la cuenta equivocada.
Después, un paso físico que ninguna API puede hacer: encender el lector, vincularlo a esa caja desde la aplicación del procesador, y activar el POD con el número de serie de su etiqueta.
{ "serial": "N950NCD300246809" }Queda en pendiente_reinicio. Reinicia la máquina y confirma con
POST/api/v1/empresas/{id}/pods/refrescar.
Cada POD trae además un siguiente_paso redactado: es lo que conviene mostrar en tu
pantalla en vez de traducir los estados por tu cuenta.
Corregir una sucursal (PATCH) empuja el cambio al procesador: dos verdades para el mismo local es peor que no poder corregirlo, porque la de ellos es la que sale en los documentos. Y al dar de baja, el local o la caja se borran allá sólo si nunca cobraron; si cobraron se quedan, porque son parte de la historia de esos pagos.
Abrir la sesión
Alguien toma la máquina, por un turno de mostrador o una ruta. Sin esto, un cobro con tarjeta se rechaza: no habría a quién imputarle la plata.
{ "pod_id": 2, "contexto": "ruta", "persona": { "ref": "driver-482" } }Crear el cobro
La deuda, sin método. No toca al procesador y la máquina no se entera de nada.
curl -X POST … /api/v1/cobros \ -H 'X-Idempotency-Key: 7f3a9c21-…' \ -d '{"empresa_id":3,"referencia":"TT-4471903","monto":23000, "descripcion":"Envío 1 bulto"}'
La referencia es única por empresa y es tu vínculo de vuelta: usa el identificador del
envío o de la venta. Admite letras, números, guión y guión bajo, hasta 64
(^[A-Za-z0-9_-]{1,64}$): un id con barras o puntos se rechaza. Repetirla con otro
monto es referencia_en_conflicto; repetirla con el mismo monto te devuelve el
cobro que ya existe. La descripción se muestra en la pantalla del terminal, así que no
puede llevar teléfonos, RUT ni correos: se valida y se rechaza.
La cabecera de idempotencia es obligatoria. Es lo que impide que un reintento de tu lado cobre dos veces.
Elegir el método
Acá, y sólo acá, entra el método. Sólo tarjeta enciende la máquina.
{ "metodo": "tarjeta", "punto": "ses_7Y2WYZTK", "expiracion": "PT3M" }La expiración es ISO 8601 y su techo depende de por dónde se cobra: el monto en la
pantalla de un lector admite de PT30S a PT3H —es una regla del aparato, no nuestra— y por
defecto son 3 minutos. Lo que se cobra a distancia, QR y link, llega hasta PT24H, con 15
minutos y 24 horas por defecto. Pasarse es expiracion_fuera_de_rango.
| Método | Qué pasa | ¿Se factura? |
|---|---|---|
tarjeta | La máquina se enciende con el monto. | Sí |
qr | Devuelve el QR de la caja. Exige punto, igual que la tarjeta: el QR es de una caja, y una caja la tiene alguien. | Sí |
link | Devuelve una URL. No notifica a nadie: el teléfono del cliente es tuyo. | Sí |
efectivo | Se registra. Nace aprobado. | No |
transferencia | Se registra. Exige decir quién recibió. | No |
cancele_en_dispositivo: ahí lo cancela quien tiene la máquina, o se deja vencer.
Muere el intento, nunca el cobro: el cliente puede pagar en efectivo y la deuda se salda igual.Enterarse del pago
Tres caminos, y conviene usar los tres.
Te avisamos
Hacemos POST a tu URL de avisos cuando algo cambia. Firmado con tu
secreto, con su evento_id para que deduplicues, y con reintentos de 1 minuto a 24 horas.
| Evento | Cuándo |
|---|---|
cobro.pagado · cobro.revertido · cobro.anulado |
La deuda cambió de estado. Trae el cobro completo. |
pod.deshabilitado · pod.habilitado |
Una máquina salió del modo PDV —o volvió—. No tienes que preguntarlo: una máquina caída es alguien cobrando fuera del libro sin saberlo. |
conexion.revocada |
El procesador rechazó renovar el acceso de ese courier. Hay que volver a conectar su cuenta. |
X-Collectpay-Event: cobro.pagado X-Collectpay-Id: evt_9K2M… X-Collectpay-Signature: ts=1789318216,v1=8f3c… { "tipo": "cobro.pagado", "cobro": { "id":"cob_HE7ME6YW", "referencia":"TT-4471903", "estado":"pagado", "monto":23000, "intentos": […] } }
La firma es HMAC-SHA256 sobre id:{evento_id};ts:{ts}; con tu secreto.
Responde 200 y deduplica por evento_id.
Preguntas
Mientras la máquina está en pantalla, consulta el cobro cada dos segundos. Es lo que hace que la persona que está cobrando vea el resultado al instante.
Fuerzas
Si el aviso no llegó, pídenos la verdad al procesador. El resultado es el mismo que si el aviso hubiera llegado.
{ "estado": "pagado", "sincronizacion": { "intentos_actualizados": 1 } }Consultar cobros e historial
| Para | Verbo |
|---|---|
| Un cobro y sus intentos | GET/api/v1/cobros/{id} |
| Buscar por tu referencia | GET/api/v1/cobros?referencia=TT-4471903 |
| Ponerte al día tras una caída | GET/api/v1/cobros?actualizado_desde=… |
| Qué le pasó a un cobro | GET/api/v1/cobros/{id}/historial |
| Conciliar el período | GET/api/v1/intentos.csv?desde=…&hasta=… |
El listado viene paginado: { "datos": […], "siguiente_cursor": 1487 }. Mientras
ese cursor no sea null, queda más — pídelo con ?cursor=. Una sola página
leída como si fuera todo es la forma silenciosa de perder cobros.
actualizado_desde es tu red de seguridad: si tu sistema estuvo caído, con eso
recuperas todo lo que se movió sin perder nada.
El dinero se lee en el intento aprobado, no en el cobro: ahí están monto,
fee y neto, junto con facturable y el
pago (tipo, marca y cuotas). El cobro lleva el total y su estado. La comisión la
calcula el procesador unos segundos después del pago, así que puede llegar vacía en el primer
aviso y completarse enseguida.
Estados, y qué hacer con cada uno
Los estados son parte del contrato, no una convención interna: cada uno está declarado como lista cerrada en /docs, así que si mañana aparece uno nuevo aparece también ahí. Acá va lo que significan y —lo que de verdad importa— qué los mueve.
La máquina (POD)
Es el que más sorprende, porque la mitad de sus transiciones no las provoca tu código: las provoca alguien parado frente al aparato.
POST /sucursales/{id}/pods │ ▼ registrado ─────────── la cuenta del procesador todavía no estaba conectada: │ existe acá y no del otro lado. Conecta la cuenta y │ vuelve a crear el POD. ▼ pendiente_vinculacion ── ya tiene caja y QR. Falta el paso físico: encender │ el lector y escanear ese QR con la app del procesador. │ POST /pods/{id}/activar { "serial": "N950…" } ▼ pendiente_reinicio ──── el lector quedó en modo PDV pero hay que reiniciarlo. │ reiniciar la máquina + POST /empresas/{id}/pods/refrescar ▼ habilitado ─────────── cobra con tarjeta. siguiente_paso: null │ │ alguien saca el lector de PDV · DELETE /pods/{id} · │ se revoca la conexión de la empresa ▼ deshabilitado ───────── vuelve con activar + refrescar
| Estado | Qué significa | ¿Cobra con tarjeta? |
|---|---|---|
registrado | Existe acá y no en el procesador. Con Mercado Pago pasa cuando se declara la máquina antes de conectar la cuenta; con un procesador que empareja —SumUp— es el estado normal hasta que alguien teclea el código que muestra el lector. | No |
pendiente_vinculacion | Tiene caja y QR. Espera que alguien escanee el QR en el lector y mande el serial. | No |
pendiente_reinicio | Quedó en modo PDV; el lector tiene que reiniciarse para tomarlo. | No |
habilitado | El lector reporta PDV y responde. | Sí |
deshabilitado | Salió de PDV, se dio de baja, o la empresa revocó su conexión. | No |
pendiente_reinicio hasta que se
reinicia. Con SumUp es el código de emparejamiento que la propia máquina muestra en
pantalla —y que se vence—, y queda habilitada de inmediato. El verbo y el campo son los mismos
(serial); lo que cambia lo dice siguiente_paso.siguiente_paso ya redactado para mostrarlo tal cual —dice qué falta y en qué
pantalla del lector—, y es null exactamente cuando la máquina puede cobrar.
Si prefieres tu propio texto, la lista de arriba es la que tienes que cubrir entera.Dos campos más que la pantalla agradece: modo_operacion es lo que reporta el
lector (PDV o STANDALONE; en standalone el monto se teclea en el
aparato y el cobro queda fuera del libro), y ultimo_visto_en es la última vez que
contestó. Los dos se refrescan con
POST/api/v1/empresas/{id}/pods/refrescar,
que además deshabilita solo al que volvió a standalone.
Los dos QR, que no son el mismo
Vale la pena separarlos porque se llaman igual y viven en campos distintos:
| Cuál | Dónde viene | Para qué |
|---|---|---|
| El QR de la caja | pod.proveedor.qr_imagen al crear el POD |
Identifica a esa caja para siempre. Se imprime y se pega en la máquina. Es también el que se escanea desde la app del procesador para vincular el lector. |
| El cobro con QR | intento.qr.datos al crear un intento con metodo: "qr" |
No crea otro QR: le carga el monto a esa misma caja. El cliente escanea y paga lo que corresponde. |
Por eso un intento con QR exige punto, igual que la tarjeta: el QR es de una caja,
y una caja la tiene alguien.
El cobro y el intento
| Cobro | Qué significa |
|---|---|
solicitado | La deuda está viva. Admite intentos. |
pagado | Un intento la saldó. No admite otro intento. |
revertido | Se devolvió el total, por /reverso o por un reembolso del procesador. |
anulado | Se cerró sin pagarse. |
| Intento | Qué significa | Qué lo saca de ahí |
|---|---|---|
creado | La order salió, el lector todavía no la tomó. | El lector, o cancelar. |
en_dispositivo | El monto está en la pantalla del lector. | El cliente paga, alguien cancela, o expira. |
esperando_pago | QR o link entregado, esperando al cliente. | El pago, la cancelación o la expiración. |
aprobado | Pagado. Acá vive el dinero: monto, fee y neto. | Nada: es final. |
rechazado | El medio de pago no pasó. detalle dice por qué. | Nada: se intenta con otro método. |
cancelado | Lo cancelaste tú o el lector. | Nada. |
expirado | Se venció la ventana. | Nada. |
requiere_accion | El procesador dice que ese estado ya no cambia y hay que mirar la máquina. | Una persona. Queda como pendiente en la rendición. |
Un cobro puede tener varios intentos y sólo uno aprobado. Los movimientos de plata que se suman para una liquidación son los intentos aprobados, nunca los cobros.
La sesión y la conexión
La sesión no tiene estado: o tiene cerrada_en o está abierta. Cerrarla
congela la rendición. Cada conexión de la empresa con un procesador sí lo tiene:
pendiente (nunca se autorizó, o se empezó y no se terminó), habilitada
(puede cobrar) y revocada (se cortó desde acá o desde la cuenta del procesador; sus
PODs quedan deshabilitados con ella, y los de las otras cuentas siguen cobrando).
revocada, puede_cobrar se apaga y cobrar responde
conexion_no_habilitada con el motivo. Esa sí hay que volver a conectarla.Qué devuelve cada verbo
La forma exacta de cada respuesta —campos, tipos, qué puede venir null y qué
valores admite cada estado— está en /docs, verbo por verbo y con un ejemplo
real al lado. Lo que conviene saber antes de leerlo:
- El dinero se lee en el intento aprobado, no en el cobro: ahí están
monto,fee,netoyfacturable. El cobro lleva el total y su estado. - La comisión puede llegar vacía en el primer aviso: el procesador la calcula unos
segundos después del pago y se completa sola.
fee: nullconestado: "aprobado"es un estado normal, no un error. - La imputación es un snapshot: se resuelve al crear el intento y no se recalcula nunca, ni cuando el aviso del procesador llega horas después con la sesión ya cerrada.
- Los listados vienen paginados y traen
siguiente_cursor. - Las fechas van en UTC, con la
Zpuesta. Conviértelas a la hora de tu gente al mostrarlas; el servidor no sabe en qué huso está quien mira. - Los ids públicos son opacos (
cob_,int_,ses_,evt_): guárdalos como texto y no les supongas forma. Los del procesador —la order, el terminal— no salen en la respuesta a propósito.
Errores
Todos vienen con una clave estable. Programa contra la clave, no contra el mensaje.
| Clave | Qué pasó |
|---|---|
terminos_no_aceptados | La empresa no firmó todavía. |
conexion_no_habilitada | No conectó su procesador, o revocó el acceso. |
punto_no_resoluble | No hay sesión abierta, o el POD no está habilitado. |
intento_en_curso | Esa máquina ya tiene un cobro en pantalla. |
cancele_en_dispositivo | Ya está en el lector: se cancela ahí. |
cobro_cerrado | Pagado, anulado o revertido: no admite otro intento. |
idempotencia_en_conflicto | Misma cabecera, otro cuerpo. |
expiracion_fuera_de_rango | Más de 3 horas en un lector, o más de 24 a distancia. |
procesador_no_soporta | El procesador de esa empresa no hace eso. No todos empujan el monto a un lector: ofrece otro método en vez de un error técnico. |
procesador_ambiguo | La empresa tiene más de una cuenta conectada y lo que pediste no dice por cuál sale. Manda procesador, o cobra desde una máquina —que ya sabe cuál es la suya. |
terminal_ya_registrada | Ese serial o código de emparejamiento ya está en otra máquina de la empresa. La respuesta dice en cuál. |
referencia_en_conflicto | Esa referencia ya existe en la empresa, con otro monto. |
descripcion_con_datos_personales | La descripción lleva un teléfono, un RUT o un correo. |
empresa_no_pertenece_al_tenant | Esa empresa no es de tu cuenta. |
Prueba y producción
El prefijo de la key decide el ambiente. Con sk_test_ puedes forzar el resultado de
un intento sin mover dinero, usando el terminal virtual SBX0000001.
El contrato completo, verbo por verbo, está en /docs.