HHego Buyer API English OpenAPI

Compra productos digitales desde tu propio sistema

Consulta el catálogo en vivo, paga con tu saldo en USDT y recibe la entrega en la misma respuesta. Todo con una API REST y eventos por webhook.

Base URLhttps://api.hegodigitaldev.com/api/v1/buyer
Se usa solo en esta página para completar los ejemplos. No se guarda ni se envía a ningún servidor. Aun así, evita pegarla en equipos compartidos.
curl -X POST https://api.hegodigitaldev.com/api/v1/buyer/orders \
  -H "X-API-Key: hg_live_TU_API_KEY" \
  -H "Idempotency-Key: 7f9c2e0a-51d4-4c1b-9a67-3e0d2b8a11f4" \
  -H "Content-Type: application/json" \
  -d '{"productId": 102, "quantity": 2, "expectedPriceUsdt": 7}'
201 CreatedLa compra y la entrega llegan juntas
{
  "success": true,
  "orderId": 45012,
  "status": "completed",
  "productId": 102,
  "quantity": 2,
  "totalPriceUsdt": 7,
  "createdAt": 1767225600000,
  "delivery": {
    "items": [
      "maria.lopez@example.com:Pass#8842",
      "juan.perez@example.com:Pass#1937"
    ]
  },
  "balanceAfterUsdt": 143.25,
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}

Guía rápida#

En cinco pasos pasas de cero a tu primera compra automatizada.

  1. Genera tu API key

    En el bot de Telegram abre Perfil → API de desarrollador → Generar y acepta las condiciones. La key empieza con hg_live_ y se muestra una sola vez: guárdala en un gestor de secretos.

    Puedes tener una sola key activa. Puedes generarla aunque todavía no hayas recargado saldo.

  2. Recarga tu billetera

    Las compras por API se cobran de tu billetera en USDT, la misma que usas en el bot. Recarga desde la sección de billetera del bot y comprueba el saldo:

    curl https://api.hegodigitaldev.com/api/v1/buyer/balance \
      -H "X-API-Key: hg_live_TU_API_KEY"
    200 OKRespuesta
    {
      "success": true,
      "balanceUsdt": 143.25,
      "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
    }
  3. Consulta el catálogo

    Elige un id y anota su priceUsdt. Si requiresRecipient es true, necesitarás el usuario de Telegram que recibirá el producto.

    curl https://api.hegodigitaldev.com/api/v1/buyer/products \
      -H "X-API-Key: hg_live_TU_API_KEY"
    200 OKRespuesta
    {
      "success": true,
      "products": [
        {
          "id": 102,
          "name": "Netflix 1 Pantalla (30 días)",
          "description": "Perfil individual con PIN. Renovable.",
          "instructions": "Ingresa con el correo y la clave entregados. No cambies la contraseña.",
          "priceUsdt": 3.5,
          "availableStock": 18,
          "requiresRecipient": false
        },
        {
          "id": 215,
          "name": "Telegram Premium 3 meses",
          "description": "Activación directa en la cuenta que indiques.",
          "instructions": "",
          "priceUsdt": 14.9,
          "availableStock": "unlimited",
          "requiresRecipient": true
        }
      ],
      "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
    }
  4. Haz tu primera compra

    Envía productId, quantity y el precio total que esperas pagar. Genera un Idempotency-Key nuevo (un UUID) para cada compra y guárdalo antes de enviar.

    curl -X POST https://api.hegodigitaldev.com/api/v1/buyer/orders \
      -H "X-API-Key: hg_live_TU_API_KEY" \
      -H "Idempotency-Key: 7f9c2e0a-51d4-4c1b-9a67-3e0d2b8a11f4" \
      -H "Content-Type: application/json" \
      -d '{"productId": 102, "quantity": 2, "expectedPriceUsdt": 7}'
    201 CreatedRespuesta
    {
      "success": true,
      "orderId": 45012,
      "status": "completed",
      "productId": 102,
      "quantity": 2,
      "totalPriceUsdt": 7,
      "createdAt": 1767225600000,
      "delivery": {
        "items": [
          "maria.lopez@example.com:Pass#8842",
          "juan.perez@example.com:Pass#1937"
        ]
      },
      "balanceAfterUsdt": 143.25,
      "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
    }
    Ya compraste

    La entrega viene en delivery.items. Además recibes una copia en tu chat de Telegram.

  5. Recibe eventos (recomendado)

    Configura un webhook para enterarte de cambios de precio y stock, entregas asíncronas y saldo bajo sin consultar la API cada minuto. Ver Webhooks.

No hay entorno de pruebas

Toda compra usa saldo real. Para practicar sin gastar usa /health, /products y /balance, simula webhooks con una firma propia (ver Verificar la firma) y haz tu primera compra con el producto más barato.

Formato de las respuestas#

Todas las respuestas son JSON en UTF-8, salvo la descarga de archivos. Cada respuesta incluye:

CampoTipoDescripción
successbooleantrue si la operación se completó, false si fue un error.
requestIdstringIdentificador único de la petición. También llega en el header X-Request-Id. Regístralo en tus logs y envíalo a soporte si algo falla.
error, messagestringSolo en errores: un código estable (INSUFFICIENT_BALANCE) y un texto legible. Programa tu lógica contra error, no contra message.

Convenciones

  • Montos: siempre en USDT, como número JSON (3.5, no "3.50"). Los precios tienen hasta 2 decimales.
  • Fechas: createdAt es un entero en milisegundos Unix (UTC).
  • IDs: orderId y productId son enteros.
  • Sin caché: las respuestas llevan Cache-Control: no-store.
  • Ignora los campos que no conozcas: podemos añadir campos nuevos sin avisar (nunca quitaremos ni cambiaremos los existentes dentro de /v1).

Autenticación y permisos#

Envía tu API key en cada petición (salvo /health), con cualquiera de estos dos headers:

Headers
X-API-Key: hg_live_TU_API_KEY

# o, equivalente
Authorization: Bearer hg_live_TU_API_KEY

Cada key tiene permisos (scopes). Las keys nuevas incluyen todos:

ScopePermite
catalog:readGET /products
balance:readGET /balance
orders:writePOST /orders (gastar saldo)
orders:readGET /orders/{id} y GET /orders/{id}/download
webhooks:readGET /webhook
webhooks:writePOST /webhook, DELETE /webhook
  • 401UNAUTHORIZED — la key falta, es inválida, fue regenerada o desactivada.
  • 403FORBIDDEN — la key es válida pero no tiene el scope necesario (Missing orders:write scope).
401Key inválida
{
  "success": false,
  "error": "UNAUTHORIZED",
  "message": "Invalid or missing API key",
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
Tu key gasta tu saldo

Quien tenga la key puede comprar con tu billetera. Guárdala solo en tu servidor (variables de entorno o gestor de secretos), nunca en código de navegador, apps móviles, repositorios o capturas. Si se filtra, regenérala en el bot: la anterior deja de funcionar al instante.

Productos y entrega#

El catálogo es vivo: precios, descripciones y stock cambian en tiempo real. Cada producto se entrega de una de estas formas:

Items inmediatos

Credenciales, links, códigos o pines. Vienen en delivery.items (lista de textos) en la misma respuesta de la compra.

Archivo

La respuesta trae delivery.downloadUrl. Descárgalo con tu key desde GET /orders/{id}/download.

Entrega asíncrona

Algunos productos los entrega un proveedor externo con demora. La compra responde pending_delivery (ya pagada) y la entrega llega después: por webhook o consultando la orden.

Con destinatario

Productos como Telegram Premium se activan en la cuenta de otra persona. Requieren recipient.username. Ver la guía.

Precio y stock

  • priceUsdt es el precio por unidad. El precio total de una compra puede diferir de priceUsdt × quantity si el producto tiene precios por volumen.
  • availableStock es un número, o "unlimited" para productos sin límite. En productos de proveedores externos es una cifra aproximada que se refresca periódicamente: si se agota antes, la compra responde OUT_OF_STOCK o PURCHASE_FAILED.
  • El stock se reserva de forma atómica por compra: dos compras simultáneas nunca reciben el mismo item.
  • Saldo retenido: mientras una compra a un proveedor externo está en curso (normalmente un instante), el importe queda retenido y tu saldo disponible en GET /balance baja. Se descuenta definitivamente al completarse y se libera solo si la compra falla.
  • Solo aparecen los productos activos.

Estados de una orden#

EstadoSignificadoQué hacer
completedPagada y entregada. delivery está disponible.Entrega el producto a tu cliente.
pending_deliveryPagada; el proveedor todavía está entregando.Espera el webhook order.completed o consulta GET /orders/{id} cada 5–10 s.
failedEl proveedor no pudo entregar. El importe se devuelve a tu saldo.Avisa a tu cliente o reintenta con una key nueva. Llega el webhook order.failed.
processingOrden en curso (por ejemplo, un pedido pendiente de pago creado desde el bot).Consulta de nuevo más tarde.

GET /orders/{id} devuelve cualquier orden de tu cuenta, incluidas las que hiciste desde el bot de Telegram.

Idempotencia y reintentos#

Las redes fallan: una petición puede llegar y su respuesta perderse. Para que reintentar nunca compre dos veces, POST /orders exige el header Idempotency-Key.

Una key por compra

Genera un UUID por cada compra que quieras hacer y guárdalo antes de enviar. Máximo 128 caracteres.

Mismo intento, misma key

Si hubo timeout o error de red, reenvía la petición idéntica con la misma key. Recibirás la orden ya creada o se comprará una sola vez.

Compra nueva, key nueva

Reutilizar una key con un cuerpo distinto devuelve 409 IDEMPOTENCY_CONFLICT. Las keys son de tu cuenta: siguen valiendo aunque regeneres tu API key.

Qué se repite y qué no

ResultadoCon la misma keySiguiente paso
Compra exitosa (201)Devuelve la misma orden y entrega, sin volver a cobrar.Listo.
INSUFFICIENT_BALANCE, PRICE_CHANGED, NOT_FOUNDRepite la misma respuesta.Corrige (recarga, actualiza el precio) y usa una key nueva.
OUT_OF_STOCKNo se guarda: se vuelve a intentar.Puedes reintentar con la misma key cuando haya stock.
502 PURCHASE_FAILEDNo se debitó tu saldo. Con stock propio se reintenta; con proveedor externo repite el error.Reintenta; si persiste, usa una key nueva o contacta a soporte con el requestId.
504 PROVIDER_TIMEOUTNo se debitó tu saldo. Repite el mismo resultado: el proveedor pudo haber procesado la compra sin que lo supiéramos.Espera unos segundos y reintenta con una key nueva.
409 IDEMPOTENCY_UNRESOLVEDUn intento anterior se interrumpió sin llegar a cobrarse.No se debitó nada. Reintenta con una key nueva.
Regla de oro

Si no sabes si una compra ocurrió, repite la misma petición con la misma key. Nunca generes una key nueva para “volver a probar” una compra que podría haberse hecho.

Límites y tiempos#

ConceptoValorDetalle
Ritmo de peticiones120 por minuto por API keyAl superarlo recibes 429 RATE_LIMITED con el header Retry-After (segundos). Si necesitas más, contacta a soporte.
Autenticaciones fallidas20 cada 5 minutos por IPDespués, los intentos con key inválida reciben 429. Las peticiones con una key válida no se ven afectadas.
Tamaño del cuerpo64 KBMás grande responde 400.
Idempotency-Keyhasta 128 caracteresUsa un UUID.
Espera al proveedorhasta 15 sLos proveedores suelen responder al instante. Si uno no responde a tiempo, la compra falla con 504 PROVIDER_TIMEOUT y no se cobra.
Timeout recomendado en tu cliente30 s en POST /orders, 15 s en el restoSi vence, reintenta con la misma Idempotency-Key.
Webhook10 s para responderVer reintentos.

Ante un 429 o un 5xx, espera con retroceso exponencial (1 s, 2 s, 4 s…) y respeta Retry-After cuando exista.

Copia en Telegram#

Aunque compres por API, la entrega también llega a tu chat con el bot, idéntica a una compra normal: resumen de la orden, un archivo .txt de respaldo, los items listos para copiar y botones de soporte, reporte de problemas y “Mis compras”.

Es una copia de cortesía: tu integración debe usar siempre la respuesta de la API. Si bloqueaste el bot o no tienes chat abierto, la compra funciona igual.

Referencia de endpoints#

Base URL: https://api.hegodigitaldev.com/api/v1/buyer. Todos los endpoints salvo /health requieren tu API key.

GET/healthsin autenticación

Comprobar que la API responde

Verificación de estado para monitoreo. No requiere autenticación ni consume tu límite de peticiones.

curl https://api.hegodigitaldev.com/api/v1/buyer/health
200 OKRespuesta
{
  "success": true,
  "service": "buyer-api",
  "status": "ok",
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
GET/productscatalog:read

Listar el catálogo

Devuelve todos los productos activos con su precio, stock y datos de entrega en el momento de la consulta.

Campos de cada producto

CampoTipoDescripción
idintegerIdentificador que usas en productId.
namestringNombre comercial.
descriptionstringQué incluye el producto.
instructionsstringIndicaciones de uso que se entregan con la compra. Puede estar vacío.
priceUsdtnumberPrecio por unidad en USDT.
availableStockinteger | "unlimited"Unidades disponibles.
requiresRecipientbooleanSi es true, POST /orders exige recipient.username.
  • 401UNAUTHORIZED — key inválida.
  • 403FORBIDDEN — falta catalog:read.

Consúltalo al iniciar y luego mantén tu copia con webhooks en lugar de consultar sin parar.

curl https://api.hegodigitaldev.com/api/v1/buyer/products \
  -H "X-API-Key: hg_live_TU_API_KEY"
200 OKRespuesta
{
  "success": true,
  "products": [
    {
      "id": 102,
      "name": "Netflix 1 Pantalla (30 días)",
      "description": "Perfil individual con PIN. Renovable.",
      "instructions": "Ingresa con el correo y la clave entregados. No cambies la contraseña.",
      "priceUsdt": 3.5,
      "availableStock": 18,
      "requiresRecipient": false
    },
    {
      "id": 215,
      "name": "Telegram Premium 3 meses",
      "description": "Activación directa en la cuenta que indiques.",
      "instructions": "",
      "priceUsdt": 14.9,
      "availableStock": "unlimited",
      "requiresRecipient": true
    }
  ],
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
GET/balancebalance:read

Consultar tu saldo

Saldo disponible de tu billetera en USDT. Es el mismo saldo que ves en el bot.

  • 401UNAUTHORIZED — key inválida.
  • 403FORBIDDEN — falta balance:read.

Cada compra exitosa también devuelve balanceAfterUsdt, así que no necesitas consultarlo después de comprar.

curl https://api.hegodigitaldev.com/api/v1/buyer/balance \
  -H "X-API-Key: hg_live_TU_API_KEY"
200 OKRespuesta
{
  "success": true,
  "balanceUsdt": 143.25,
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
POST/ordersorders:write

Crear una compra

Compra un producto con tu saldo. En un solo paso valida el precio, reserva el stock, descuenta tu billetera y entrega. Es idempotente: reintentar con la misma Idempotency-Key nunca compra dos veces.

Headers

HeaderValor
X-API-Key obligatorioTu API key.
Idempotency-Key obligatorioUUID único por compra (máx. 128 caracteres).
Content-Type obligatorioapplication/json

Cuerpo

CampoTipoDescripción
productId obligatoriointegerID del producto (de GET /products).
quantity opcionalintegerUnidades, mínimo 1. Por defecto 1.
expectedPriceUsdt obligatorionumberPrecio total que esperas pagar por toda la cantidad, no por unidad. Si el precio actual difiere, no se cobra nada y recibes PRICE_CHANGED. Protege a tu cliente de cambios de precio.
recipient.username opcionalstringUsuario de Telegram que recibe el producto, con @ (5–32 caracteres: letras, números y _). Obligatorio si requiresRecipient es true.

Respuesta exitosa

CampoDescripción
orderIdID de la orden. Guárdalo: lo necesitas para consultarla.
statuscompleted o pending_delivery. Ver estados.
productId, quantityLo que compraste.
totalPriceUsdtImporte cobrado.
createdAtMilisegundos Unix.
delivery.itemsLista de textos entregados (solo si completed y no es archivo).
delivery.downloadUrlRuta relativa para descargar el archivo (productos tipo archivo).
balanceAfterUsdtSaldo restante tras la compra.

Errores

  • 400BAD_REQUEST — falta la Idempotency-Key, el JSON es inválido o no es un objeto, hay campos inválidos, falta recipient.username o el cuerpo excede 64 KB.
  • 402INSUFFICIENT_BALANCE — tu saldo no alcanza. No se cobró nada.
  • 404NOT_FOUND — el producto no existe o no está disponible.
  • 409PRICE_CHANGED — el precio cambió; la respuesta trae currentPriceUsdt.
  • 409OUT_OF_STOCK — no hay stock suficiente.
  • 409IDEMPOTENCY_CONFLICT — esa key ya se usó con otro cuerpo.
  • 409IDEMPOTENCY_UNRESOLVED — un intento anterior se interrumpió; usa una key nueva.
  • 502PURCHASE_FAILED — no se pudo completar; tu saldo no se debitó.
  • 504PROVIDER_TIMEOUT — el proveedor no respondió en 15 s; tu saldo no se debitó. Reintenta con una key nueva.
¿Y si el precio cambia?

Tómalo como una función, no como un error: muestra el nuevo precio (currentPriceUsdt) a tu cliente o aplica tu regla de tolerancia y vuelve a llamar con una Idempotency-Key nueva y el nuevo expectedPriceUsdt.

curl -X POST https://api.hegodigitaldev.com/api/v1/buyer/orders \
  -H "X-API-Key: hg_live_TU_API_KEY" \
  -H "Idempotency-Key: 7f9c2e0a-51d4-4c1b-9a67-3e0d2b8a11f4" \
  -H "Content-Type: application/json" \
  -d '{"productId": 102, "quantity": 2, "expectedPriceUsdt": 7}'
201 CreatedCompra completada
{
  "success": true,
  "orderId": 45012,
  "status": "completed",
  "productId": 102,
  "quantity": 2,
  "totalPriceUsdt": 7,
  "createdAt": 1767225600000,
  "delivery": {
    "items": [
      "maria.lopez@example.com:Pass#8842",
      "juan.perez@example.com:Pass#1937"
    ]
  },
  "balanceAfterUsdt": 143.25,
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
201 CreatedProducto asíncrono: pagado, entrega pendiente
{
  "success": true,
  "orderId": 45013,
  "status": "pending_delivery",
  "productId": 215,
  "quantity": 1,
  "totalPriceUsdt": 14.9,
  "createdAt": 1767225600000,
  "balanceAfterUsdt": 128.35,
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
201 CreatedProducto tipo archivo
{
  "success": true,
  "orderId": 45014,
  "status": "completed",
  "productId": 320,
  "quantity": 1,
  "totalPriceUsdt": 9,
  "createdAt": 1767225600000,
  "delivery": {
    "downloadUrl": "/api/v1/buyer/orders/45014/download"
  },
  "balanceAfterUsdt": 119.35,
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
409 ConflictEl precio cambió
{
  "success": false,
  "error": "PRICE_CHANGED",
  "message": "Price changed",
  "currentPriceUsdt": 7.2,
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
402 Payment RequiredSaldo insuficiente
{
  "success": false,
  "error": "INSUFFICIENT_BALANCE",
  "message": "Insufficient wallet balance",
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
504 Gateway TimeoutEl proveedor no respondió
{
  "success": false,
  "error": "PROVIDER_TIMEOUT",
  "message": "The supplier did not answer in time. Your balance was not debited.",
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
GET/orders/{id}orders:read

Consultar una orden

Estado actual de una orden de tu cuenta y, si está completed, su entrega. Úsalo para resolver pending_delivery o para reconciliar tus registros.

  • 404NOT_FOUND — la orden no existe o no es tuya.
  • 403FORBIDDEN — falta orders:read.

El cuerpo tiene los mismos campos que la compra, salvo balanceAfterUsdt. No hay un endpoint para listar órdenes: guarda el orderId de cada compra.

curl https://api.hegodigitaldev.com/api/v1/buyer/orders/45012 \
  -H "X-API-Key: hg_live_TU_API_KEY"
200 OKRespuesta
{
  "success": true,
  "orderId": 45012,
  "status": "completed",
  "productId": 102,
  "quantity": 2,
  "totalPriceUsdt": 7,
  "createdAt": 1767225600000,
  "delivery": {
    "items": [
      "maria.lopez@example.com:Pass#8842",
      "juan.perez@example.com:Pass#1937"
    ]
  },
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
404 Not FoundOrden inexistente
{
  "success": false,
  "error": "NOT_FOUND",
  "message": "Order not found",
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
GET/orders/{id}/downloadorders:read

Descargar el archivo de una orden

Para productos tipo archivo. Devuelve el archivo original (no JSON) con su Content-Type y Content-Disposition: attachment. El nombre no lleva extensión: usa el Content-Type para decidir cómo guardarlo.

  • 404NOT_FOUND — la orden no existe, no es tuya o su producto no es un archivo.
  • 409DELIVERY_NOT_READY — la orden aún no está completed.
  • 502 / 503DELIVERY_UNAVAILABLE — no se pudo recuperar el archivo ahora; reintenta en unos segundos.
curl -L https://api.hegodigitaldev.com/api/v1/buyer/orders/45014/download \
  -H "X-API-Key: hg_live_TU_API_KEY" \
  -o pedido-45014
GET/webhookwebhooks:read

Ver tu webhook

Devuelve la configuración actual. El secret nunca se vuelve a mostrar completo: solo verás sus últimos 4 caracteres en secretMasked.

  • 404NOT_FOUND — no hay webhook configurado.
  • 409WEBHOOK_SECRET_UNREADABLE — el secret guardado no se puede leer; vuelve a registrar el webhook.
cURL
curl https://api.hegodigitaldev.com/api/v1/buyer/webhook \
  -H "X-API-Key: hg_live_TU_API_KEY"
200 OKRespuesta
{
  "success": true,
  "webhook": {
    "url": "https://tienda.example.com/hooks/hego",
    "events": [
      "order.completed",
      "order.failed",
      "stock.changed",
      "balance.low"
    ],
    "balanceThresholdUsdt": 20,
    "secretMasked": "***5f80"
  },
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
POST/webhookwebhooks:write

Configurar tu webhook

Registra la URL que recibirá los eventos y a cuáles te suscribes. Solo hay un webhook por cuenta: volver a llamar reemplaza la configuración y genera un secret nuevo (el anterior deja de servir al instante).

CampoTipoDescripción
url obligatoriostringURL https pública. Sin usuario/contraseña, puerto 443 u 8443, y un dominio que resuelva a una IP pública (no localhost ni redes privadas). No seguimos redirecciones.
events obligatoriostring[]Uno o más de: order.completed, order.failed, catalog.updated, stock.changed, balance.low.
balanceThresholdUsdt opcionalnumberUmbral (≥ 0) para balance.low.
  • 400BAD_REQUEST — URL o evento inválidos (HTTPS URL is required, Invalid URL destination, Port not allowed, Host does not resolve, Invalid event: …).
El secret se muestra una sola vez

Guárdalo en tu gestor de secretos: lo necesitas para verificar la firma de cada evento.

curl -X POST https://api.hegodigitaldev.com/api/v1/buyer/webhook \
  -H "X-API-Key: hg_live_TU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://tienda.example.com/hooks/hego",
    "events": ["order.completed", "order.failed", "stock.changed", "balance.low"],
    "balanceThresholdUsdt": 20
  }'
200 OKRespuesta
{
  "success": true,
  "webhook": {
    "url": "https://tienda.example.com/hooks/hego",
    "events": [
      "order.completed",
      "order.failed",
      "stock.changed",
      "balance.low"
    ],
    "balanceThresholdUsdt": 20,
    "secret": "whsec_live_5b1e0c7a94d3f28a6e1b0d47c93f5a82e6d1074b3c9a5f80"
  },
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
DELETE/webhookwebhooks:write

Eliminar tu webhook

Desactiva el webhook. Como es una acción destructiva, exige confirmación explícita en el cuerpo: {"confirm": true}. Los eventos pendientes de envío se cancelan.

  • 400BAD_REQUEST — falta confirm: true.
  • 404NOT_FOUND — no hay webhook configurado.
cURL
curl -X DELETE https://api.hegodigitaldev.com/api/v1/buyer/webhook \
  -H "X-API-Key: hg_live_TU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"confirm": true}'
200 OKRespuesta
{
  "success": true,
  "message": "Webhook deleted",
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}

Webhooks#

En lugar de consultar la API continuamente, deja que Hego te avise: enviamos un POST con JSON a tu URL cuando ocurre un evento al que te suscribiste. Configúralo con POST /webhook.

Lo que recibe tu servidor

HeaderContenido
X-Hego-EventTipo de evento, por ejemplo order.completed.
X-Hego-DeliveryID único del evento (evt_…). Úsalo para deduplicar.
X-Hego-SignatureFirma: t=<timestamp>,v1=<hmac>. Ver verificación.
X-Request-IdIdentificador de este envío, para soporte.
User-AgentHegoMarket-BuyerAPI/1.0

El cuerpo es un JSON con los datos del evento más eventId y type. Ambos van dentro de la firma, así que puedes confiar en ellos.

Responde con cualquier 2xx en menos de 10 segundos. Cualquier otro resultado (o un timeout) se considera fallido y se reintenta.

Eventos y payloads#

EventoCuándoQué hacer
order.completedUna orden quedó entregada: tras una compra inmediata o cuando un proveedor termina una entrega asíncrona.Si estaba pending_delivery, consulta GET /orders/{id} para leer la entrega.
order.failedUn proveedor no pudo entregar. El importe ya fue devuelto a tu saldo.Avisa a tu cliente o reintenta con una key nueva.
catalog.updatedCambió un producto: precio, nombre, descripción o estado. Puede agrupar varios productos.Vuelve a leer esos productos con GET /products.
stock.changedCambió el inventario de un producto (reposición, venta, retiro). Puede agrupar varios.Actualiza tu stock local.
balance.lowTu saldo bajó de tu umbral por una compra (se emite una vez al cruzarlo).Recarga tu billetera.
POSTorder.completed
{
  "orderId": 45013,
  "eventId": "evt_9f2b7c1d4a6e08355b2c9d1e7f30a4b6",
  "type": "order.completed"
}
POSTorder.failed
{
  "orderId": 45013,
  "eventId": "evt_9f2b7c1d4a6e08355b2c9d1e7f30a4b6",
  "type": "order.failed"
}
POSTcatalog.updated
{
  "productId": 102,
  "productIds": [
    98,
    102
  ],
  "eventId": "evt_9f2b7c1d4a6e08355b2c9d1e7f30a4b6",
  "type": "catalog.updated"
}
POSTstock.changed
{
  "productId": 102,
  "productIds": [
    98,
    102
  ],
  "eventId": "evt_9f2b7c1d4a6e08355b2c9d1e7f30a4b6",
  "type": "stock.changed"
}
POSTbalance.low
{
  "balanceUsdt": 18.4,
  "thresholdUsdt": 20,
  "eventId": "evt_9f2b7c1d4a6e08355b2c9d1e7f30a4b6",
  "type": "balance.low"
}
Eventos agrupados

Cuando cambian muchos productos seguidos (una reposición masiva, por ejemplo), catalog.updated y stock.changed se agrupan en una sola entrega: productIds lista todos los productos y productId es el último.

Verificar la firma#

Cualquiera puede enviar un POST a tu URL. La firma demuestra que lo envió Hego y que nadie alteró el cuerpo. Verifícala siempre antes de actuar.

  1. Lee t y v1 del header X-Hego-Signature.
  2. Construye el texto firmado: t + . + X-Hego-Delivery + . + cuerpo crudo exacto.
  3. Calcula HMAC-SHA256 con tu secret (whsec_live_…) y compáralo en hexadecimal con v1, en tiempo constante.
  4. Rechaza si t (milisegundos) tiene más de 5 minutos de diferencia con tu reloj: evita reproducir envíos viejos.
import crypto from 'node:crypto';
import express from 'express';

const app = express();
const TOLERANCE_MS = 5 * 60 * 1000;

// IMPORTANTE: usa el cuerpo CRUDO (Buffer), no el JSON ya parseado
app.post('/hooks/hego', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('X-Hego-Signature') || '';
  const eventId = req.get('X-Hego-Delivery') || '';
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));

  const fresh = Math.abs(Date.now() - Number(parts.t)) <= TOLERANCE_MS;
  const expected = crypto
    .createHmac('sha256', process.env.HEGO_WEBHOOK_SECRET)
    .update(`${parts.t}.${eventId}.${req.body.toString('utf8')}`)
    .digest('hex');
  const given = Buffer.from(parts.v1 || '');
  const valid = given.length === expected.length &&
    crypto.timingSafeEqual(given, Buffer.from(expected));

  if (!fresh || !valid) return res.sendStatus(401);

  const event = JSON.parse(req.body.toString('utf8'));
  res.sendStatus(200); // responde rápido y procesa después
  queueForProcessing(event); // deduplica por event.eventId
});
Usa el cuerpo crudo

Si tu framework parsea el JSON y lo vuelve a serializar, los bytes cambian y la firma no coincidirá. Lee el cuerpo como bytes/texto antes de parsearlo.

Prueba tu verificación

Con tu secret puedes firmar un envío de prueba y mandarlo a tu propio endpoint:

Bash
# Simula una entrega firmada contra tu endpoint (útil para probar tu verificación)
SECRET="whsec_live_..."
BODY='{"orderId":45013,"eventId":"evt_test_1","type":"order.completed"}'
TS=$(date +%s%3N)
SIG=$(printf '%s' "$TS.evt_test_1.$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')

curl -X POST https://tienda.example.com/hooks/hego \
  -H "Content-Type: application/json" \
  -H "X-Hego-Event: order.completed" \
  -H "X-Hego-Delivery: evt_test_1" \
  -H "X-Hego-Signature: t=$TS,v1=$SIG" \
  -d "$BODY"

Reintentos y orden#

Hasta 5 intentos

Inmediato, y luego a 1 min, 5 min, 30 min y 6 h. Tras el quinto fallo el evento se descarta y avisamos a soporte.

Al menos una vez

Un mismo evento puede llegar más de una vez. Deduplica por eventId (o el header X-Hego-Delivery) y haz tu manejo idempotente.

Sin orden estricto

Los eventos de tu cuenta salen en orden, pero un reintento puede llegar después de uno más nuevo. Ante la duda, consulta el estado actual con la API.

  • Responde rápido (2xx) y procesa en segundo plano.
  • No seguimos redirecciones: una respuesta 3xx cuenta como fallo. Registra la URL final.
  • Antes de cada envío volvemos a comprobar que tu dominio resuelve a una IP pública.
  • Si tu endpoint estuvo caído más de unas horas, reconcilia con GET /products y GET /orders/{id}.

Códigos de error#

Todos los errores comparten el mismo formato y un error estable sobre el que puedes programar.

CódigoHTTPCuándo ocurreQué hacer
BAD_REQUEST400Petición mal formada o campos inválidos.Corrige la petición; no reintentes igual.
UNAUTHORIZED401API key ausente, inválida o revocada.Revisa la key; regenérala si se filtró.
INSUFFICIENT_BALANCE402Saldo insuficiente.Recarga y reintenta con una key nueva.
FORBIDDEN403La key no tiene el scope necesario.Usa una key con ese permiso.
NOT_FOUND404Producto, orden, webhook o ruta inexistente.Verifica el ID.
PRICE_CHANGED409El precio total actual no coincide con expectedPriceUsdt.Usa currentPriceUsdt con una key nueva.
OUT_OF_STOCK409Stock insuficiente.Reintenta más tarde (misma key) o reduce la cantidad.
IDEMPOTENCY_CONFLICT409La key ya se usó con otro cuerpo.Usa una key nueva para una compra distinta.
IDEMPOTENCY_UNRESOLVED409Un intento anterior se interrumpió sin cobrarse.Reintenta con una key nueva.
DELIVERY_NOT_READY409Descarga de una orden que aún no está completada.Espera a completed.
WEBHOOK_SECRET_UNREADABLE409El secret guardado no puede leerse.Registra el webhook de nuevo.
RATE_LIMITED429Demasiadas peticiones.Espera Retry-After segundos.
INTERNAL_ERROR500Error inesperado nuestro.Reintenta con retroceso; reporta el requestId si persiste.
PURCHASE_FAILED502La compra no pudo completarse (por ejemplo, falló el proveedor). Tu saldo no se debitó.Reintenta; si persiste, contacta a soporte con el requestId.
PROVIDER_TIMEOUT504El proveedor externo no respondió en 15 s. Tu saldo no se debitó.Espera unos segundos y reintenta con una key nueva.
DELIVERY_UNAVAILABLE502 / 503No se pudo recuperar el archivo.Reintenta en unos segundos.
402Ejemplo: saldo insuficiente
{
  "success": false,
  "error": "INSUFFICIENT_BALANCE",
  "message": "Insufficient wallet balance",
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
409Ejemplo: sin stock
{
  "success": false,
  "error": "OUT_OF_STOCK",
  "message": "Insufficient stock",
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
429Ejemplo: límite de peticiones
{
  "success": false,
  "error": "RATE_LIMITED",
  "message": "Rate limit exceeded",
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
400Ejemplo: petición inválida
{
  "success": false,
  "error": "BAD_REQUEST",
  "message": "Invalid order payload",
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}

Flujo de compra robusto#

Un ejemplo completo en Node.js que combina lo esencial: una Idempotency-Key por compra, reintentos seguros con la misma key, manejo de 429 y 5xx, y espera de entregas asíncronas.

JavaScript
import { randomUUID } from 'node:crypto';

const BASE = 'https://api.hegodigitaldev.com/api/v1/buyer';
const headers = (extra = {}) => ({ 'X-API-Key': process.env.HEGO_API_KEY, ...extra });
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// 1) Compra con reintentos seguros: MISMA key mientras sea el mismo intento
async function buy({ productId, quantity, expectedPriceUsdt, recipient }) {
  const idempotencyKey = randomUUID(); // persístela en tu base de datos antes de llamar
  const body = JSON.stringify({ productId, quantity, expectedPriceUsdt, recipient });

  for (let attempt = 1; attempt <= 4; attempt++) {
    try {
      const res = await fetch(`${BASE}/orders`, {
        method: 'POST',
        headers: headers({ 'Idempotency-Key': idempotencyKey, 'Content-Type': 'application/json' }),
        body,
        signal: AbortSignal.timeout(30_000), // el servidor espera al proveedor hasta 15 s
      });
      const data = await res.json();

      if (res.status === 201) return data;
      if (res.status === 504) throw Object.assign(new Error(data.message), { code: data.error, data }); // proveedor lento: no se cobró; reintenta más tarde con una key NUEVA
      if (res.status === 429) { await sleep(Number(res.headers.get('retry-after') || 1) * 1000); continue; }
      if (res.status >= 500 || res.status === 409 && data.error === 'OUT_OF_STOCK') { await sleep(2 ** attempt * 500); continue; }
      throw Object.assign(new Error(data.message), { code: data.error, data }); // 402, 400, 404, 409 de precio: no se reintentan
    } catch (err) {
      if (err.code) throw err;              // error de negocio: sube al llamador
      await sleep(2 ** attempt * 500);       // timeout/red: reintenta con la MISMA key
    }
  }
  throw new Error('No se pudo confirmar la compra');
}

// 2) Si quedó pending_delivery, espera el webhook o consulta el pedido
async function waitForDelivery(orderId, { every = 5000, timeout = 600_000 } = {}) {
  const deadline = Date.now() + timeout;
  while (Date.now() < deadline) {
    const res = await fetch(`${BASE}/orders/${orderId}`, { headers: headers() });
    const order = await res.json();
    if (order.status === 'completed' || order.status === 'failed') return order;
    await sleep(every);
  }
  throw new Error('Entrega aún pendiente');
}

// 3) Uso
const order = await buy({ productId: 215, quantity: 1, expectedPriceUsdt: 14.9, recipient: { username: '@cliente_final' } });
const final = order.status === 'pending_delivery' ? await waitForDelivery(order.orderId) : order;
console.log(final.status, final.delivery);

Por qué así

  • La key se crea una vez por compra y se reutiliza en los reintentos: si la respuesta se perdió, recibes la misma orden.
  • Los errores de negocio (402, 400, 404, precio) no se reintentan: repetirlos no los arregla.
  • Guarda el orderId y el resultado en tu base de datos dentro de la misma operación que entrega el producto a tu cliente.

Mantener tu catálogo sincronizado#

Los precios y el stock cambian todo el tiempo. El patrón recomendado:

  1. Al arrancar, carga todo con GET /products y guárdalo.
  2. Suscríbete a catalog.updated y stock.changed.
  3. Al recibir un evento, vuelve a leer el catálogo (o solo los productIds indicados) y actualiza tu copia.
  4. Como red de seguridad, refresca el catálogo completo cada 10–15 minutos.
  5. Envía siempre expectedPriceUsdt: si tu copia quedó vieja, la API te protege con PRICE_CHANGED.

Productos con destinatario#

Los productos con requiresRecipient: true (por ejemplo Telegram Premium o Stars) se activan directamente en una cuenta de Telegram. Indica quién lo recibe en recipient.username:

cURL
curl -X POST https://api.hegodigitaldev.com/api/v1/buyer/orders \
  -H "X-API-Key: hg_live_TU_API_KEY" \
  -H "Idempotency-Key: 0c5d7a2e-93b1-4f68-8e0a-6d41b7c9e2f5" \
  -H "Content-Type: application/json" \
  -d '{"productId": 215, "quantity": 1, "expectedPriceUsdt": 14.9,
       "recipient": {"username": "@cliente_final"}}'
201 CreatedSuele quedar pendiente hasta que el proveedor activa
{
  "success": true,
  "orderId": 45013,
  "status": "pending_delivery",
  "productId": 215,
  "quantity": 1,
  "totalPriceUsdt": 14.9,
  "createdAt": 1767225600000,
  "balanceAfterUsdt": 128.35,
  "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}
  • El usuario debe empezar con @ y tener 5–32 caracteres (letras, números o _).
  • Verifica el usuario antes de comprar: una activación en la cuenta equivocada no se puede deshacer.
  • Si te olvidas del destinatario, recibes 400 BAD_REQUEST y no se cobra.

Productos tipo archivo#

Cuando el producto es un archivo, la compra devuelve delivery.downloadUrl (una ruta relativa). Descárgalo con tu API key:

curl -L https://api.hegodigitaldev.com/api/v1/buyer/orders/45014/download \
  -H "X-API-Key: hg_live_TU_API_KEY" \
  -o pedido-45014

Puedes descargarlo de nuevo cuando quieras mientras la orden exista. Guarda el archivo en tu almacenamiento: tu cliente no debería usar tu API key para descargarlo.

Checklist de producción#

Seguridad

Key en gestor de secretos · nunca en frontend · HTTPS siempre · regenerar si se filtra · secret del webhook aparte.

Compras

UUID guardado antes de enviar · misma key en reintentos · expectedPriceUsdt siempre · timeout de 30 s · guardar orderId.

Webhooks

Firma y timestamp verificados · cuerpo crudo · responde 2xx rápido · deduplicación por eventId · procesamiento asíncrono.

Operación

Log del requestId · retroceso exponencial ante 429/5xx · alerta con balance.low · reconciliación periódica.

Preguntas frecuentes#

¿Hay un entorno de pruebas (sandbox)?

No. Las compras usan saldo real. Prueba con /health, /products y /balance, simula webhooks firmados con tu secret y haz tu primera compra con el producto más barato.

Mi petición dio timeout. ¿Me cobraron?

Repite la misma petición con la misma Idempotency-Key. Si la compra se hizo, recibes esa orden; si no, se hará una sola vez. Nunca cambies la key.

Recibí 502 PURCHASE_FAILED.

Tu saldo no se debitó. Reintenta; si con la misma key sigue igual, usa una key nueva. Si persiste, escribe a soporte con el requestId.

Recibí 504 PROVIDER_TIMEOUT.

El proveedor no respondió a tiempo y no se debitó tu saldo (el importe retenido se libera solo). Es raro: espera unos segundos y reintenta con una key nueva. Si ocurre seguido con un producto, avísanos con el requestId.

¿Por qué mi saldo bajó y luego volvió?

Mientras se procesa una compra con un proveedor externo el importe queda retenido para que nadie más pueda gastarlo. Si la compra falla, se libera; si se completa, se descuenta definitivamente.

¿Por qué recibo PRICE_CHANGED si el precio es el mismo?

expectedPriceUsdt es el precio total de la cantidad, no el unitario. Con quantity: 3 a 3.5 c/u envía 10.5. Con precios por volumen el total puede diferir: usa currentPriceUsdt de la respuesta.

¿Cómo obtengo el listado de mis órdenes?

Hoy se consultan por ID con GET /orders/{id}. Guarda el orderId de cada compra en tu base de datos.

¿Cómo roto mi API key?

En el bot: Perfil → API de desarrollador → Regenerar. La anterior deja de funcionar al instante y tus Idempotency-Key previas siguen siendo válidas. También puedes desactivar la API desde ahí.

¿Puedo llamar a la API desde el navegador?

No lo hagas: expondrías una key que gasta tu saldo. Llama siempre desde tu servidor.

¿Cuántas keys puedo tener?

Una activa por cuenta. Regenerarla revoca la anterior.

¿Qué moneda usa?

USDT, tanto para precios como para el saldo.

¿Dónde pido ayuda?

Desde el botón de soporte del bot. Incluye siempre el requestId de la respuesta (o el header X-Request-Id) y el orderId si aplica.

Cambios#

VersiónFechaCambios
v12026-09Compras con proveedores externos: espera máxima de 15 s (504 PROVIDER_TIMEOUT, sin cobro) y saldo retenido mientras la compra está en curso para que no pueda gastarse dos veces.
v12026-09Endurecimiento: límites de peticiones, validación estricta de webhooks (destinos públicos, sin redirecciones), type y eventId dentro de la firma, eventos agrupados con productIds, OUT_OF_STOCK (409), IDEMPOTENCY_UNRESOLVED, recuperación de compras interrumpidas y estado failed coherente con el webhook.
v12026-09Lanzamiento: catálogo, saldo, compras idempotentes, consulta y descarga de órdenes, y webhooks firmados.

Los cambios compatibles (campos nuevos, eventos nuevos) se añaden dentro de /v1. Un cambio incompatible saldría como /v2 con aviso previo.