PaymentLink) es una URL pública de pago que puedes compartir sin conocer al deudor de antemano. Al crear la liga, TapiPay genera una deuda asociada, que es la que recibe el pago; su ID viene en el campo debtId de la respuesta.
Cuándo usarla: cobros sin padrón de deudores (donaciones, ventas sueltas, links de checkout por WhatsApp o email), donde conoces al pagador en el momento del pago, no antes. Si ya conoces al deudor, una deuda suele ser mejor.
Dos modos de uso
Un solo uso
singleUse: true (default). Se desactiva tras el primer pago. Ideal para una factura o venta puntual.Reutilizable
singleUse: false. Se crea sin deudor y acepta múltiples pagos mientras siga activa. Modo donaciones o pasarela de pagos.Crear una liga
Lo mínimo es el monto, una descripción visible en la página de pago y la fecha de expiración.Campos principales
number
requerido
Monto en pesos, con hasta 2 decimales (mínimo
0.01).string
requerido
Descripción visible en la página de pago. Debe ser string (si no,
400 INVALID_REQUEST).string
requerido
Fecha y hora de expiración, obligatoria y en el futuro. Debe ser un date-time RFC 3339, con
Z o con offset (2026-12-31T23:59:59Z, 2026-12-31T17:59:59-06:00). Un valor como 2026-12-31 23:59 o una fecha sola devuelven 400 INVALID_REQUEST. Se guarda en UTC (ver Fechas de la liga).string
Llave natural de la liga. Opcional, pero si la envías debe ser única por organización: un duplicado devuelve
409 RESOURCE_CONFLICT y no se crea ninguna deuda. Ver Idempotencia.boolean
predeterminado:"true"
true se desactiva tras el primer pago; false acepta múltiples pagos.string
Identificador propio para la liga (opcional). Se sanea a un formato seguro para URL (minúsculas, alfanuméricos y guiones, máximo 64 caracteres). Si tras sanear queda vacío, devuelve
400 INVALID_REQUEST.string
Identificador de un deudor que ya existe como contacto. Excluyente con
contactData. Si no corresponde a un contacto existente, la API responde 422 BUSINESS_RULE_VIOLATION y no se crea nada. Para crear el contacto en el mismo request, usa contactData.object
Datos del deudor inline (alternativa a
externalClientId).string
URL de redirección al completar el pago. Si no la envías, se usa la página de Tapi.
object
Datos propios de tu sistema. Debe ser un objeto plano, con las mismas claves reservadas que en las deudas (ver Convenciones de la API). Se guarda en la deuda asociada a la liga y no se devuelve en la liga.
currency (default MXN), productName y metadata (objeto plano). Los campos que no están declarados se ignoran.
Idempotencia
La llave de idempotencia de una liga es suexternalPaymentLinkId:
- Si lo envías en el body, se usa ese valor.
- Si no lo envías, pero mandas el header
Idempotency-Key, el valor del header se usa comoexternalPaymentLinkId. Si envías los dos, el header se ignora. - Si no envías ninguno de los dos, la liga no tiene llave propia y cada request crea una liga nueva. En la respuesta,
externalPaymentLinkIdviene ennull.
externalPaymentLinkId que ya existe devuelve 409 RESOURCE_CONFLICT y no se crea ninguna deuda. Para recuperar la liga original, búscala con GET /payment-links?externalPaymentLinkId=....
La respuesta
La liga siempre traeexternalPaymentLinkId, expiresAt, successUrl, metadata, identifierValue, contact y product, con null cuando no hay valor. contact es siempre null en esta versión.
El identifierValue y la URL de pago
El paymentUrl es una URL opaca: úsala tal como viene, sin parsearla ni armarla a mano. Con Private Links habilitado lleva un código corto y no contiene el identifierValue. Sin Private Links, o si no se puede generar el código corto, es la URL con el formato de liga de pago del portal. Si la integración con el portal falla, viene en null y la liga se crea igual. Las ligas creadas antes de este cambio conservan su URL anterior.
Cómo se arma el identifierValue depende de si la liga tiene un deudor conocido:
- Sin deudor conocido
- Con deudor conocido
Es el caso típico de una liga. El
identifierValue se expone en la respuesta, así que puedes usarlo para correlacionar la liga con tu sistema.- Si enviaste
identifierValue, se usa tu valor saneado. - Si no, TapiPay genera uno sintético con el formato
{3letras}-{aleatorio}(las 3 letras salen del nombre de tu organización; por ejemplo, “Acme Corp” produceacm-x3kM9pQr).
Fechas de la liga
expiresAt se devuelve siempre en UTC con milisegundos y Z (YYYY-MM-DDTHH:mm:ss.sssZ), aunque la hayas enviado con offset:
Las ligas antiguas guardadas con fecha sola (
2026-12-01) se devuelven como el inicio de ese día UTC (2026-12-01T00:00:00.000Z). La deuda asociada vence en la fecha UTC de expiresAt: en el primer ejemplo, el 2027-02-11.
Ciclo de vida
Actualizar sin invalidar la URL
PATCH /payment-links/{id} permite cambiar tres campos sin cambiar el paymentUrl: la liga sigue accesible desde la misma URL.
string
Nueva descripción. Debe ser un string no vacío.
string
Nuevo vencimiento, date-time RFC 3339 en el futuro.
null devuelve 400: las ligas siempre vencen. Se guarda en UTC y actualiza también el vencimiento de la deuda asociada a la fecha UTC del nuevo valor (por ejemplo, 2027-02-10T23:30:00-06:00 hace que la deuda venza el 2027-02-11).object
Nuevos metadatos, objeto plano. Reemplaza por completo los anteriores.
null o un valor que no es objeto devuelven 400.PATCH actualiza solo la liga. description y metadata solo cambian la liga, no su deuda. Un body vacío o cualquier otro campo (amount, currency, singleUse, etc.) devuelven 400 INVALID_REQUEST. Una liga en estado PAID, EXPIRED o CANCELLED no se puede editar: devuelve 422 BUSINESS_RULE_VIOLATION, y ese estado se valida antes que el body.
Cancelar
POST /payment-links/{id}/cancel pasa la liga a CANCELLED y cancela también su deuda asociada. Si la cancelación de la deuda falla, la liga queda cancelada igual. Cancelar una liga que ya está cancelada devuelve 200 sin cambios; una liga PAID o EXPIRED no se puede cancelar (422 BUSINESS_RULE_VIOLATION). La respuesta mantiene el paymentUrl.
Listar ligas
GET /payment-links devuelve tus ligas paginadas, ordenadas por externalPaymentLinkId descendente (no por fecha de creación).
Este es el único listado cuyo
meta trae capped: vale true cuando meta.total es aproximado porque se alcanzó el máximo de ítems que el servicio recorre.
Pagos de una liga
GET /payment-links/{id}/payments devuelve las deudas asociadas a la liga, con su estado y amountPaid. Una liga recién creada devuelve su deuda PENDING con amountPaid: 0. El paymentId es el identificador interno de esa deuda (debt_ + un número): no es el debtId de la liga, así que para leer la deuda con GET /debts/{id} usa el debtId. Las deudas canceladas no se listan.
GET /debts: status, createdAtFrom / createdAtTo (con la misma regla de fechas por día UTC), minAmount, maxAmount, externalClientId, batchId, page y limit. Su meta no trae capped.
Endpoints
Pruébalo en el API Reference
Explora cada endpoint con su playground interactivo.

