QvaPay API
Integraciones

Botón de pago

Pega dos líneas en tu web y cobra con «Pagar con QvaPay». Tu cliente paga en una ventana emergente y vuelve a tu web, que recibe el webhook firmado de siempre.

Cómo funciona

El botón abre el pago de QvaPay en una ventana emergente, sin que tu cliente salga de tu web. Al terminar la ventana se cierra sola, tu página recibe un evento y va a tu URL de Éxito. Tu servidor se entera por el mismo webhook firmado que una factura creada por API.

Hay dos formas de usarlo:

FormaImporteNecesitas
Botón fijoSiempre el mismoNada más que pegar el código
FacturaEl que calcules en cada pedidoUn servidor que llame a POST /v2/create_invoice

Quien paga necesita una cuenta de QvaPay con la identidad verificada y saldo. Si no ha iniciado sesión, entra dentro de la misma ventana y vuelve al pago.

Antes de empezar

  1. Crea una App en Mis Aplicaciones y déjala activa.
  2. Pon su Webhook URL: es la confirmación real de cada pago.
  3. Pon su URL de Éxito: a ella vuelve tu cliente después de pagar. Recibe transaction_uuid y remote_id en la query.

Botón fijo

Crea el botón

Abre tu App en Mis Aplicaciones, ve a Botón de pago y escribe el nombre y el importe.

Pega el código en tu web

<div data-qvapay-button="BUTTON_UUID"></div>
<script src="https://www.qvapay.com/embed/v1/button.js" async></script>

Puedes cambiar el importe o el nombre cuando quieras sin volver a pegar nada. Si desactivas un botón, deja de cobrar y tu cliente ve un aviso de que no está disponible.

Cada clic de un cliente con sesión crea una factura de la App que caduca pronto, a los 30 minutos por defecto, con remote_id BTN-<uuid del botón>-<aleatorio>. Cada factura se confirma por webhook como cualquier otra.

El botón fijo no sirve para casar un pago con un pedido concreto de tu sistema. Si necesitas eso, usa la forma con factura y tu propio remote_id.

Con factura

Crea la factura desde tu servidor y pon su transaction_uuid en la página:

curl -X POST "https://api.qvapay.com/v2/create_invoice" \
  -H "app-id: TU_APP_ID" \
  -H "app-secret: TU_APP_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 25.00, "description": "Pedido 12345", "remote_id": "order-12345" }'
<div data-qvapay-invoice="TRANSACTION_UUID"></div>
<script src="https://www.qvapay.com/embed/v1/button.js" async></script>

Nunca llames a create_invoice desde el navegador: tu app-secret quedaría a la vista.

Confirmar el pago

Los eventos del navegador son para tu interfaz, nunca una prueba de pago: cualquiera puede dispararlos desde la consola. Entrega el producto solo al recibir el webhook firmado, o tras consultar POST /v2/transactions/{uuid}.

El webhook llega a tu Webhook URL con transaction_uuid, remote_id, status y amount en el cuerpo, y las cabeceras x-qvapay-signature y x-qvapay-timestamp. Consulta cómo verificar la firma HMAC.

Eventos en tu página

Los eventos salen del elemento del botón y suben hasta document:

EventoCuándoevent.detail
qvapay:paidLa factura está pagadatransaction_uuid, redirect
qvapay:cancelEl cliente pulsó Cancelartransaction_uuid
qvapay:closeEl cliente cerró la ventana sin pagar ni cancelarVacío

Tras qvapay:paid, el script lleva a tu cliente a redirect, que es tu URL de Éxito. Para quedarte en la página, cancela el evento:

document.addEventListener('qvapay:paid', (event) => {
  event.preventDefault()
  mostrarGracias(event.detail.transaction_uuid)
})

Con tu propio botón

window.QvaPayButton abre el pago sin usar nuestro botón. Llámalo dentro del clic del usuario: si esperas a una petición, el navegador bloquea la ventana.

miBoton.addEventListener('click', () => {
  window.QvaPayButton.open({
    invoice: 'TRANSACTION_UUID', // o button: 'BUTTON_UUID'
    onPaid: (event) => console.log(event.detail.transaction_uuid),
    onCancel: (event) => console.log('Canceló'),
    onClose: (event) => console.log('Cerró la ventana'),
  })
})

En una aplicación de una sola página, los elementos que llegan después de cargar el script se activan con mount:

window.QvaPayButton.mount(elemento, { onPaid: (event) => console.log(event.detail) })
MétodoParámetrosDevuelve
open(opciones)invoice o button, y onPaid, onCancel, onClose opcionalesLa ventana, o null si no se pudo abrir
mount(elemento, opciones)Un elemento con data-qvapay-invoice o data-qvapay-button, y las mismas funciones opcionalesNada

Casos especiales

CasoQué pasa
El navegador bloquea la ventana, o tu web corre en un WebViewLa misma pestaña va a la página de pago y vuelve por tu URL de Éxito. No hay eventos.
La factura ya estaba pagadaLa ventana lo avisa con qvapay:paid y se cierra.
La factura caducó sin pagarLa ventana lo indica. Crea otra factura.
La App está en modo pruebaEl botón fijo no cobra y lo indica. create_invoice en modo prueba devuelve la factura ya pagada, sin página de pago.

Si tu web usa cabeceras de seguridad, necesita:

  • Content-Security-Policy: añade https://www.qvapay.com a script-src.
  • Cross-Origin-Opener-Policy: usa same-origin-allow-popups. Con same-origin tu página no puede hablar con la ventana y qvapay:close salta al abrirla.

«Pagar» se habilita unos instantes después de abrir la ventana y otra vez cada vez que recupera el foco. Es una protección contra clics forzados: un clic que trae la ventana al frente no paga.

Gestionar botones por API

Las mismas operaciones del panel, con Bearer Token en el header Authorization. No aceptan credenciales de App ni tokens de alcance limitado.

GET /app/:uuid/pay-buttons

Lista los botones de la App.

{
  "data": [
    {
      "uuid": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "name": "Curso de marketing",
      "description": "Acceso de por vida",
      "amount": "25.00",
      "active": true,
      "created_at": "2026-11-05T12:00:00.000Z",
      "updated_at": "2026-11-05T12:00:00.000Z"
    }
  ],
  "limit": 20
}

limit es el máximo de botones que admite cada App.

POST /app/:uuid/pay-buttons

Crea un botón. Devuelve 201 con { "data": { ... } }.

ParámetroTipoRequeridoDescripción
namestringSiNombre, hasta 80 caracteres
descriptionstringNoConcepto de la factura, hasta 255 caracteres. Si falta se usa name
amountnumberSiImporte en USD, de 0.01 a 100,000, con 2 decimales como máximo

PATCH /app/:uuid/pay-buttons/:button

Cambia name, description, amount o active. Solo los campos enviados. No hay borrado: un botón se desactiva con "active": false.

Errores

CódigoDescripción
400Datos inválidos, o nada que actualizar
403La cuenta dueña de la App debe completar su verificación (KYC_REQUIRED)
404La App o el botón no existen, o no son tuyos
409Llegaste al máximo de botones de esta App (PAY_BUTTON_LIMIT)
503No se pudo guardar el botón. Inténtalo más tarde