DOCUMENTACIÓN
API de
Comfirme.

Crea cobros por Bre-B desde tu página, tu tienda o tu bot de WhatsApp, entérate al instante cuando te pagan y valida comprobantes de cualquier banco.

Cómo funciona

  1. Tu sistema crea un cobro y recibe el QR con el monto exacto que debe pagar tu cliente.
  2. Le muestras el QR (o la llave y el monto). Tu cliente paga desde Nequi, Bancolombia o cualquier banco por Bre-B.
  3. Nosotros vemos el aviso de tu banco y confirmamos el pago. Te enteras consultando el cobro o, al instante, por webhook.

Base: https://app.comfirme.com/api/v1. Todo es JSON y los montos van en pesos enteros.

Autenticación

Manda tu llave en la cabecera Authorization: Bearer cf_live_…. Úsala solo desde tu servidor: nunca la pongas en el código de tu página web ni en la app.

  • cf_test_…: de prueba. No toca plata real y puedes simular pagos.
  • cf_live_…: real. Usa tus llaves Bre-B y confirma con los avisos de tu banco.

Límite: 60 peticiones por minuto por llave (10 por minuto para subir comprobantes).

Tipos de cobro

  • automatico (por defecto): si nadie tiene apartado ese monto, tu cliente paga el precio exacto y se confirma solo con el aviso del banco (verificacion: "aviso"). Si ya hay otro cobro abierto por el mismo monto, este se valida con comprobante (verificacion: "comprobante"), también con el precio exacto. Mira siempre el campo verificacion para saber si debes pedirle la foto del comprobante a tu cliente.
  • monto_unico: siempre por aviso. El monto a pagar es el precio menos unos pesos (por ejemplo $19.999 por un cobro de $20.000): esa diferencia es lo que identifica a tu cliente. Muestra montoAPagar, no tu precio.
  • comprobante: siempre con comprobante. Tu cliente paga el precio exacto y tú subes la foto de su comprobante. La foto sola no aprueba nada: buscamos el aviso del banco que cuadre con ella (monto, nombre, hora y banco).

Tus llaves Bre-B

GET /llaves

Tus llaves en el orden de Ajustes → Mi Bre-B, con su id, su tipo (personal o negocios) y su monto máximo. Por cada correo puedes tener una llave personal y una de Negocios.

Al crear un cobro, llave elige por cuál se cobra. Si no la mandas (o mandas "automatica"), se elige sola entre las que aceptan el monto. Con una personal y una de Negocios caben dos cobros del mismo precio a la vez, los dos con el monto exacto, uno por cada llave: el aviso de tu banco dice a cuál llegó cada pago.

curl https://app.comfirme.com/api/v1/llaves \
  -H "Authorization: Bearer $COMFIRME_LLAVE"
{
  "llaves": [
    { "id": "a1b2c3", "llave": "0093354356", "tipo": "negocios", "nombre": "Nequi Negocios",
      "orden": 1, "verificada": true, "montoMaximo": null },
    { "id": "d4e5f6", "llave": "@tunegocio", "tipo": "personal", "nombre": "Personal de Nequi",
      "orden": 2, "verificada": true, "montoMaximo": 2000000 }
  ]
}

Crear un cobro

POST /cobros

montoObligatorio. Pesos enteros, de $500 a $50.000.000.
tipoautomatico (por defecto), monto_unico o comprobante.
llaveOpcional. El id de una de tus llaves (GET /llaves), o automatica (por defecto).
descripcionOpcional. Hasta 140 caracteres.
referenciaOpcional. Tu número de pedido. Si la repites mientras el cobro sigue abierto, te devolvemos el mismo (200) en vez de crear otro: protege contra doble clic y reintentos.
metadataOpcional. Hasta 20 campos tuyos; te los devolvemos tal cual.
curl -X POST https://app.comfirme.com/api/v1/cobros \
  -H "Authorization: Bearer $COMFIRME_LLAVE" \
  -H "Content-Type: application/json" \
  -d '{"monto": 20000, "descripcion": "Corte + barba", "referencia": "pedido-1234"}'

Respuesta (201):

{
  "id": "cob_8f3a1c…",
  "tipo": "automatico",
  "verificacion": "aviso",
  "estado": "pendiente",
  "modo": "live",
  "montoSolicitado": 20000,
  "montoAPagar": 20000,
  "qr": "000201010212…",
  "qrImagen": "https://…/api/v1/cobros/cob_8f3a1c…/qr?firma=…",
  "llave": "@tunegocio",
  "llaveId": "a1b2c3",
  "tipoLlave": "negocios",
  "descripcion": "Corte + barba",
  "referencia": "pedido-1234",
  "metadata": { "cliente": "Juan" },
  "creadoEn": "2026-10-05T19:30:00.000Z",
  "expiraEn": "2026-10-05T19:45:00.000Z",
  "pago": null
}

qrImagen es un PNG listo para un <img> o para mandar por WhatsApp; no necesita la llave. qr es el texto del QR, por si lo quieres dibujar tú.

Verificar un cobro

GET /cobros/{id}

Consúltalo cada 3 a 5 segundos mientras tu cliente tiene el QR delante, o espera el webhook.

curl https://app.comfirme.com/api/v1/cobros/cob_8f3a1c… \
  -H "Authorization: Bearer $COMFIRME_LLAVE"
pendienteEsperando el pago.
pagadoLlegó el pago y cuadra. Ya puedes entregar.
revisarLlegó el pago pero hay una duda (por ejemplo, el nombre). Revísalo en tu panel.
esperando_avisoEl comprobante se ve bien, pero el aviso del banco aún no llega. Se confirma solo cuando llegue.
rechazadoEl comprobante no sirvió. Mira comprobante.motivo; puedes subir otro (hasta 5).
expiradoPasó el tiempo sin pago (15 min por aviso, 24 h con comprobante).
canceladoLo cancelaste.

Si alguien paga un cobro expirado (hasta 48 h después), igual pasa a pagado y te llega el webhook.

Subir un comprobante

POST /cobros/{id}/comprobante

Solo en cobros con verificacion: "comprobante". JPG, PNG o WebP de hasta 3 MB, como archivo (multipart/form-data, campo imagen) o en JSON como data URL. Leemos todo lo que se pueda (banco, monto, nombre, fecha, hora, referencia…) y lo devolvemos en comprobante.datos.

curl -X POST https://app.comfirme.com/api/v1/cobros/cob_8f3a1c…/comprobante \
  -H "Authorization: Bearer $COMFIRME_LLAVE" \
  -F "[email protected]"
no_es_comprobanteLa imagen no es un comprobante de transferencia.
ilegibleNo se alcanza a leer el monto.
transferencia_fallidaEl comprobante dice que la transferencia falló.
monto_distintoEl monto no es el que había que pagar.
destinatario_distintoLe pagaron a otra llave.
fecha_distintaEl comprobante es de antes de crear el cobro.
comprobante_repetidoEsa misma imagen ya se usó en otro cobro.
aviso_ya_usadoEl pago de ese comprobante ya se le acreditó a otro cobro.
ambiguoHay varios pagos que cuadran y no se puede saber cuál es.
aviso_no_llegoPasaron 48 h y el aviso del banco nunca llegó.

Cancelar un cobro

POST /cobros/{id}/cancelar

Cancela los cobros que tu cliente abandonó: así el monto exacto queda libre enseguida para el siguiente y no tiene que pagar con comprobante ni con pesos de menos.

curl -X POST https://app.comfirme.com/api/v1/cobros/cob_8f3a1c…/cancelar \
  -H "Authorization: Bearer $COMFIRME_LLAVE"

Simular un pago (solo pruebas)

POST /cobros/{id}/simular — solo con llaves cf_test_.

  • En un cobro por aviso lo marca como pagado. Manda {"resultado": "revisar"} para probar el otro caso.
  • En uno con comprobante crea un aviso de banco falso (remitente, banco, monto). Después sube un comprobante de verdad con ese nombre y monto, y verás cómo cruza.
# Cobro por aviso: lo marca como pagado
curl -X POST https://app.comfirme.com/api/v1/cobros/cob_8f3a1c…/simular \
  -H "Authorization: Bearer $COMFIRME_LLAVE_PRUEBA" \
  -H "Content-Type: application/json" \
  -d '{"resultado": "pagado", "remitente": "Juan Pérez"}'

# Cobro con comprobante: crea el aviso falso con el que cruzará tu comprobante
curl -X POST https://app.comfirme.com/api/v1/cobros/cob_8f3a1c…/simular \
  -H "Authorization: Bearer $COMFIRME_LLAVE_PRUEBA" \
  -H "Content-Type: application/json" \
  -d '{"remitente": "Juan Pérez", "banco": "Nequi", "monto": 20000}'

Webhooks

Configura tu URL en Ajustes → Desarrolladores. Te mandamos un POST con JSON cuando un cobro cambia: cobro.pagado, cobro.revisar, cobro.esperando_aviso, cobro.comprobante_rechazado y cobro.cancelado. El cuerpo trae el cobro completo, igual que en la consulta.

{
  "id": "evt_4b2e…",
  "tipo": "cobro.pagado",
  "modo": "live",
  "creadoEn": "2026-10-05T19:32:10.000Z",
  "cobro": {
    "id": "cob_8f3a1c…",
    "estado": "pagado",
    "montoAPagar": 20000,
    "referencia": "pedido-1234",
    "pago": { "monto": 20000, "remitente": "VICTOR GELVES", "banco": "Nequi", "recibidoEn": "…" },
    "…": "el resto del cobro"
  }
}

Responde con un 2xx en menos de 10 segundos. Si falla, reintentamos a 1, 5 y 30 minutos. Puede llegar el mismo evento dos veces: usa su id para no procesarlo dos veces. Antes de entregar un pedido, si tienes dudas, consulta el cobro.

Verifica la firma: la cabecera Comfirme-Firma: t=…,v1=… es el HMAC-SHA256 de `${t}.${cuerpo}` con tu secreto. Usa el cuerpo crudo, sin volver a convertirlo en JSON.

import crypto from "node:crypto";

// Express: app.post("/webhooks/comfirme", express.raw({ type: "application/json" }), manejar)
function firmaValida(cuerpoCrudo, cabecera, secreto) {
  const partes = Object.fromEntries(cabecera.split(",").map((p) => p.split("=")));
  const esperada = crypto
    .createHmac("sha256", secreto)
    .update(`${partes.t}.${cuerpoCrudo}`)
    .digest("hex");
  const vieja = Math.abs(Date.now() / 1000 - Number(partes.t)) > 300; // 5 minutos
  return !vieja && crypto.timingSafeEqual(Buffer.from(esperada), Buffer.from(partes.v1 ?? ""));
}

function manejar(req, res) {
  if (!firmaValida(req.body.toString(), req.get("Comfirme-Firma") ?? "", process.env.COMFIRME_WEBHOOK_SECRETO)) {
    return res.sendStatus(400);
  }
  const evento = JSON.parse(req.body.toString());
  if (evento.tipo === "cobro.pagado") {
    // marca el pedido evento.cobro.referencia como pagado
  }
  res.sendStatus(200);
}

Errores

Siempre con la forma {"error": {"codigo": "…", "mensaje": "…"}}.

400json_invalido
401llave_faltante, llave_invalida
403sin_llave_breb, qr_bloqueado, solo_en_pruebas
404cobro_no_existe
409sin_montos_disponibles, sin_llave_para_monto, llave_no_acepta_monto, referencia_en_uso, estado_no_permite, sin_intentos
413imagen_muy_grande
422monto_invalido, tipo_invalido, llave_no_existe, datos_invalidos, imagen_invalida
429demasiadas_peticiones (mira Retry-After)
502analisis_no_disponible: no pudimos leer el comprobante ahora; reintenta

¿Dudas con la integración? Escríbenos a [email protected].