QvaPay API
P2P

Feed del mercado (stream)

Los mismos eventos del P2P por SSE, sin montar un endpoint público.

La alternativa al webhook si tu integración ya es un proceso que corre de continuo: abres una conexión y lees. Sin servidor público, sin URL, sin verificar firmas.

Va incluido en la misma suscripción de $10 / 30 días, y puedes usar los dos canales a la vez.

Conectar

curl -N "https://api.qvapay.com/v2/p2p/stream" \
  -H "app-id: tu-app-id" \
  -H "app-secret: tu-app-secret"
retry: 10000
event: init
data: {"ok":true,"expires_at":"2026-10-20T18:00:00Z"}

event: p2p.created
data: {"event":"p2p.created","sent_at":"2026-09-20T18:00:01Z","data":{"uuid":"7c9e…","type":"sell","coin":"USDT","amount":"100.00","receive":"39500.00","status":"open","updated_at":"2026-09-20T18:00:01Z","only_vip":false,"offer_kind":"fixed","available_amount":null,"order_min":null,"order_max":null,"owner_uuid":"3f1b…","owner_vip":true,"requirements":null,"change":null,"pause":null}}

: keepalive 1758391226000

Los eventos y el data son exactamente los mismos que en el webhook (incluidos only_vip, owner_uuid, owner_vip, los de ofertas por rango y el resto de características de la oferta). Qué eventos llegan al stream se elige en el panel aparte de los del webhook: ver Elegir qué llega a cada canal. Los comentarios : keepalive llegan cada 25 segundos para que ningún proxy dé la conexión por muerta; ignóralos.

const es = new EventSource(url)   // en Node, usa una librería que permita cabeceras
es.addEventListener('p2p.created', (e) => {
	const { data } = JSON.parse(e.data)
	console.log('nueva oferta', data.uuid, data.coin, data.amount)
})

Reconexión

Da por hecho que la conexión se corta cada pocos minutos. No es un fallo: las funciones donde corre el stream tienen un tiempo máximo de vida, así que se cierra y hay que volver a conectar. Por eso mandamos retry: 10000, que le dice al cliente que reintente a los 10 segundos.

Un EventSource de navegador reconecta solo. Si lo implementas tú, reconecta con backoff y no des por hecho que la conexión dura.

Mientras estás desconectado no se acumulan eventos: el stream es en vivo, no una cola. Si no puedes permitirte perder ninguno, usa el webhook — ese sí tiene cola y reintentos.

Cuándo se cierra

EventoQué significa
initConectado. Trae tu expires_at
subscription_expiredTu suscripción venció; la conexión se cierra a continuación
event: subscription_expired
data: {"reason":"expired","renew":"https://www.qvapay.com/dev"}

Errores

CódigocodeDescripción
401APP_AUTH_REQUIREDEl stream solo acepta app-id / app-secret
402FEED_SUBSCRIPTION_REQUIREDSin suscripción activa. La respuesta trae reason

¿Stream o webhook?

  • Stream si ya tienes un proceso corriendo. Es lo más simple: nada que exponer a internet, nada que firmar.
  • Webhook si no quieres mantener nada vivo, o si perder un evento te importa. Es el único con cola y reintentos.

Muchos integradores usan el stream para reaccionar rápido y el webhook como red de seguridad. Como cada canal tiene su propia selección de eventos, puedes dejar el stream solo con lo que dispara tu bot (p2p.created, p2p.reopened, p2p.range_created…) y el webhook con todo. Pero no uses el stream como única fuente para algo crítico: lo que pase mientras reconecta no se reenvía.