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.
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}'import { randomUUID } from 'node:crypto';
const idempotencyKey = randomUUID(); // guárdala ANTES de enviar
const res = await fetch('https://api.hegodigitaldev.com/api/v1/buyer/orders', {
method: 'POST',
headers: {
'X-API-Key': process.env.HEGO_API_KEY,
'Idempotency-Key': idempotencyKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({ productId: 102, quantity: 2, expectedPriceUsdt: 7 }),
});
const order = await res.json();
if (res.status === 201) {
console.log(order.orderId, order.status, order.delivery?.items);
} else {
console.error(order.error, order.message);
}import os, uuid, requests
idempotency_key = str(uuid.uuid4()) # guárdala ANTES de enviar
res = requests.post(
'https://api.hegodigitaldev.com/api/v1/buyer/orders',
headers={
'X-API-Key': os.environ['HEGO_API_KEY'],
'Idempotency-Key': idempotency_key,
},
json={'productId': 102, 'quantity': 2, 'expectedPriceUsdt': 7},
timeout=30,
)
order = res.json()
if res.status_code == 201:
print(order['orderId'], order['status'], order.get('delivery'))
else:
print(order['error'], order['message']){
"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.
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.
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"const res = await fetch('https://api.hegodigitaldev.com/api/v1/buyer/balance', { headers: { 'X-API-Key': process.env.HEGO_API_KEY }, }); const { balanceUsdt } = await res.json(); console.log(`Saldo: ${balanceUsdt} USDT`);import os, requests res = requests.get( 'https://api.hegodigitaldev.com/api/v1/buyer/balance', headers={'X-API-Key': os.environ['HEGO_API_KEY']}, timeout=15, ) print(res.json()['balanceUsdt'])200 OKRespuesta{ "success": true, "balanceUsdt": 143.25, "requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30" }Consulta el catálogo
Elige un
idy anota supriceUsdt. SirequiresRecipientestrue, 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"const res = await fetch('https://api.hegodigitaldev.com/api/v1/buyer/products', { headers: { 'X-API-Key': process.env.HEGO_API_KEY }, }); const { products } = await res.json(); for (const p of products) { console.log(p.id, p.name, p.priceUsdt, p.availableStock); }import os, requests res = requests.get( 'https://api.hegodigitaldev.com/api/v1/buyer/products', headers={'X-API-Key': os.environ['HEGO_API_KEY']}, timeout=15, ) for p in res.json()['products']: print(p['id'], p['name'], p['priceUsdt'], p['availableStock'])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" }Haz tu primera compra
Envía
productId,quantityy el precio total que esperas pagar. Genera unIdempotency-Keynuevo (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}'import { randomUUID } from 'node:crypto'; const idempotencyKey = randomUUID(); // guárdala ANTES de enviar const res = await fetch('https://api.hegodigitaldev.com/api/v1/buyer/orders', { method: 'POST', headers: { 'X-API-Key': process.env.HEGO_API_KEY, 'Idempotency-Key': idempotencyKey, 'Content-Type': 'application/json', }, body: JSON.stringify({ productId: 102, quantity: 2, expectedPriceUsdt: 7 }), }); const order = await res.json(); if (res.status === 201) { console.log(order.orderId, order.status, order.delivery?.items); } else { console.error(order.error, order.message); }import os, uuid, requests idempotency_key = str(uuid.uuid4()) # guárdala ANTES de enviar res = requests.post( 'https://api.hegodigitaldev.com/api/v1/buyer/orders', headers={ 'X-API-Key': os.environ['HEGO_API_KEY'], 'Idempotency-Key': idempotency_key, }, json={'productId': 102, 'quantity': 2, 'expectedPriceUsdt': 7}, timeout=30, ) order = res.json() if res.status_code == 201: print(order['orderId'], order['status'], order.get('delivery')) else: print(order['error'], order['message'])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 comprasteLa entrega viene en
delivery.items. Además recibes una copia en tu chat de Telegram.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.
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:
| Campo | Tipo | Descripción |
|---|---|---|
success | boolean | true si la operación se completó, false si fue un error. |
requestId | string | Identificador ú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, message | string | Solo 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:
createdAtes un entero en milisegundos Unix (UTC). - IDs:
orderIdyproductIdson 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:
X-API-Key: hg_live_TU_API_KEY
# o, equivalente
Authorization: Bearer hg_live_TU_API_KEYCada key tiene permisos (scopes). Las keys nuevas incluyen todos:
| Scope | Permite |
|---|---|
catalog:read | GET /products |
balance:read | GET /balance |
orders:write | POST /orders (gastar saldo) |
orders:read | GET /orders/{id} y GET /orders/{id}/download |
webhooks:read | GET /webhook |
webhooks:write | POST /webhook, DELETE /webhook |
- 401
UNAUTHORIZED— la key falta, es inválida, fue regenerada o desactivada. - 403
FORBIDDEN— la key es válida pero no tiene el scope necesario (Missing orders:write scope).
{
"success": false,
"error": "UNAUTHORIZED",
"message": "Invalid or missing API key",
"requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}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
priceUsdtes el precio por unidad. El precio total de una compra puede diferir depriceUsdt × quantitysi el producto tiene precios por volumen.availableStockes 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 respondeOUT_OF_STOCKoPURCHASE_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 /balancebaja. Se descuenta definitivamente al completarse y se libera solo si la compra falla. - Solo aparecen los productos activos.
Estados de una orden#
| Estado | Significado | Qué hacer |
|---|---|---|
completed | Pagada y entregada. delivery está disponible. | Entrega el producto a tu cliente. |
pending_delivery | Pagada; el proveedor todavía está entregando. | Espera el webhook order.completed o consulta GET /orders/{id} cada 5–10 s. |
failed | El 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. |
processing | Orden 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
| Resultado | Con la misma key | Siguiente paso |
|---|---|---|
Compra exitosa (201) | Devuelve la misma orden y entrega, sin volver a cobrar. | Listo. |
INSUFFICIENT_BALANCE, PRICE_CHANGED, NOT_FOUND | Repite la misma respuesta. | Corrige (recarga, actualiza el precio) y usa una key nueva. |
OUT_OF_STOCK | No se guarda: se vuelve a intentar. | Puedes reintentar con la misma key cuando haya stock. |
502 PURCHASE_FAILED | No 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_TIMEOUT | No 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_UNRESOLVED | Un intento anterior se interrumpió sin llegar a cobrarse. | No se debitó nada. Reintenta con una key nueva. |
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#
| Concepto | Valor | Detalle |
|---|---|---|
| Ritmo de peticiones | 120 por minuto por API key | Al superarlo recibes 429 RATE_LIMITED con el header Retry-After (segundos). Si necesitas más, contacta a soporte. |
| Autenticaciones fallidas | 20 cada 5 minutos por IP | Después, los intentos con key inválida reciben 429. Las peticiones con una key válida no se ven afectadas. |
| Tamaño del cuerpo | 64 KB | Más grande responde 400. |
| Idempotency-Key | hasta 128 caracteres | Usa un UUID. |
| Espera al proveedor | hasta 15 s | Los 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 cliente | 30 s en POST /orders, 15 s en el resto | Si vence, reintenta con la misma Idempotency-Key. |
| Webhook | 10 s para responder | Ver 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.
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/healthconst res = await fetch('https://api.hegodigitaldev.com/api/v1/buyer/health');
console.log(await res.json());import requests
print(requests.get('https://api.hegodigitaldev.com/api/v1/buyer/health', timeout=10).json()){
"success": true,
"service": "buyer-api",
"status": "ok",
"requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}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
| Campo | Tipo | Descripción |
|---|---|---|
id | integer | Identificador que usas en productId. |
name | string | Nombre comercial. |
description | string | Qué incluye el producto. |
instructions | string | Indicaciones de uso que se entregan con la compra. Puede estar vacío. |
priceUsdt | number | Precio por unidad en USDT. |
availableStock | integer | "unlimited" | Unidades disponibles. |
requiresRecipient | boolean | Si es true, POST /orders exige recipient.username. |
- 401
UNAUTHORIZED— key inválida. - 403
FORBIDDEN— faltacatalog: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"const res = await fetch('https://api.hegodigitaldev.com/api/v1/buyer/products', {
headers: { 'X-API-Key': process.env.HEGO_API_KEY },
});
const { products } = await res.json();
for (const p of products) {
console.log(p.id, p.name, p.priceUsdt, p.availableStock);
}import os, requests
res = requests.get(
'https://api.hegodigitaldev.com/api/v1/buyer/products',
headers={'X-API-Key': os.environ['HEGO_API_KEY']},
timeout=15,
)
for p in res.json()['products']:
print(p['id'], p['name'], p['priceUsdt'], p['availableStock']){
"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"
}Consultar tu saldo
Saldo disponible de tu billetera en USDT. Es el mismo saldo que ves en el bot.
- 401
UNAUTHORIZED— key inválida. - 403
FORBIDDEN— faltabalance: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"const res = await fetch('https://api.hegodigitaldev.com/api/v1/buyer/balance', {
headers: { 'X-API-Key': process.env.HEGO_API_KEY },
});
const { balanceUsdt } = await res.json();
console.log(`Saldo: ${balanceUsdt} USDT`);import os, requests
res = requests.get(
'https://api.hegodigitaldev.com/api/v1/buyer/balance',
headers={'X-API-Key': os.environ['HEGO_API_KEY']},
timeout=15,
)
print(res.json()['balanceUsdt']){
"success": true,
"balanceUsdt": 143.25,
"requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}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
| Header | Valor |
|---|---|
X-API-Key obligatorio | Tu API key. |
Idempotency-Key obligatorio | UUID único por compra (máx. 128 caracteres). |
Content-Type obligatorio | application/json |
Cuerpo
| Campo | Tipo | Descripción |
|---|---|---|
productId obligatorio | integer | ID del producto (de GET /products). |
quantity opcional | integer | Unidades, mínimo 1. Por defecto 1. |
expectedPriceUsdt obligatorio | number | Precio 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 opcional | string | Usuario de Telegram que recibe el producto, con @ (5–32 caracteres: letras, números y _). Obligatorio si requiresRecipient es true. |
Respuesta exitosa
| Campo | Descripción |
|---|---|
orderId | ID de la orden. Guárdalo: lo necesitas para consultarla. |
status | completed o pending_delivery. Ver estados. |
productId, quantity | Lo que compraste. |
totalPriceUsdt | Importe cobrado. |
createdAt | Milisegundos Unix. |
delivery.items | Lista de textos entregados (solo si completed y no es archivo). |
delivery.downloadUrl | Ruta relativa para descargar el archivo (productos tipo archivo). |
balanceAfterUsdt | Saldo restante tras la compra. |
Errores
- 400
BAD_REQUEST— falta laIdempotency-Key, el JSON es inválido o no es un objeto, hay campos inválidos, faltarecipient.usernameo el cuerpo excede 64 KB. - 402
INSUFFICIENT_BALANCE— tu saldo no alcanza. No se cobró nada. - 404
NOT_FOUND— el producto no existe o no está disponible. - 409
PRICE_CHANGED— el precio cambió; la respuesta traecurrentPriceUsdt. - 409
OUT_OF_STOCK— no hay stock suficiente. - 409
IDEMPOTENCY_CONFLICT— esa key ya se usó con otro cuerpo. - 409
IDEMPOTENCY_UNRESOLVED— un intento anterior se interrumpió; usa una key nueva. - 502
PURCHASE_FAILED— no se pudo completar; tu saldo no se debitó. - 504
PROVIDER_TIMEOUT— el proveedor no respondió en 15 s; tu saldo no se debitó. Reintenta con una key nueva.
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}'import { randomUUID } from 'node:crypto';
const idempotencyKey = randomUUID(); // guárdala ANTES de enviar
const res = await fetch('https://api.hegodigitaldev.com/api/v1/buyer/orders', {
method: 'POST',
headers: {
'X-API-Key': process.env.HEGO_API_KEY,
'Idempotency-Key': idempotencyKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({ productId: 102, quantity: 2, expectedPriceUsdt: 7 }),
});
const order = await res.json();
if (res.status === 201) {
console.log(order.orderId, order.status, order.delivery?.items);
} else {
console.error(order.error, order.message);
}import os, uuid, requests
idempotency_key = str(uuid.uuid4()) # guárdala ANTES de enviar
res = requests.post(
'https://api.hegodigitaldev.com/api/v1/buyer/orders',
headers={
'X-API-Key': os.environ['HEGO_API_KEY'],
'Idempotency-Key': idempotency_key,
},
json={'productId': 102, 'quantity': 2, 'expectedPriceUsdt': 7},
timeout=30,
)
order = res.json()
if res.status_code == 201:
print(order['orderId'], order['status'], order.get('delivery'))
else:
print(order['error'], order['message']){
"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"
}{
"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"
}{
"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"
}{
"success": false,
"error": "PRICE_CHANGED",
"message": "Price changed",
"currentPriceUsdt": 7.2,
"requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}{
"success": false,
"error": "INSUFFICIENT_BALANCE",
"message": "Insufficient wallet balance",
"requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}{
"success": false,
"error": "PROVIDER_TIMEOUT",
"message": "The supplier did not answer in time. Your balance was not debited.",
"requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}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.
- 404
NOT_FOUND— la orden no existe o no es tuya. - 403
FORBIDDEN— faltaorders: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"const res = await fetch('https://api.hegodigitaldev.com/api/v1/buyer/orders/45012', {
headers: { 'X-API-Key': process.env.HEGO_API_KEY },
});
const order = await res.json();
console.log(order.status, order.delivery);import os, requests
res = requests.get(
'https://api.hegodigitaldev.com/api/v1/buyer/orders/45012',
headers={'X-API-Key': os.environ['HEGO_API_KEY']},
timeout=15,
)
print(res.json()['status']){
"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"
}{
"success": false,
"error": "NOT_FOUND",
"message": "Order not found",
"requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}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.
- 404
NOT_FOUND— la orden no existe, no es tuya o su producto no es un archivo. - 409
DELIVERY_NOT_READY— la orden aún no estácompleted. - 502 / 503
DELIVERY_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-45014import { writeFile } from 'node:fs/promises';
const res = await fetch('https://api.hegodigitaldev.com/api/v1/buyer/orders/45014/download', {
headers: { 'X-API-Key': process.env.HEGO_API_KEY },
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
await writeFile('pedido-45014', Buffer.from(await res.arrayBuffer()));
console.log(res.headers.get('content-type'));import os, requests
res = requests.get(
'https://api.hegodigitaldev.com/api/v1/buyer/orders/45014/download',
headers={'X-API-Key': os.environ['HEGO_API_KEY']},
timeout=60,
)
res.raise_for_status()
open('pedido-45014', 'wb').write(res.content)
print(res.headers['Content-Type'])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.
- 404
NOT_FOUND— no hay webhook configurado. - 409
WEBHOOK_SECRET_UNREADABLE— el secret guardado no se puede leer; vuelve a registrar el webhook.
curl https://api.hegodigitaldev.com/api/v1/buyer/webhook \
-H "X-API-Key: hg_live_TU_API_KEY"{
"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"
}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).
| Campo | Tipo | Descripción |
|---|---|---|
url obligatorio | string | URL 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 obligatorio | string[] | Uno o más de: order.completed, order.failed, catalog.updated, stock.changed, balance.low. |
balanceThresholdUsdt opcional | number | Umbral (≥ 0) para balance.low. |
- 400
BAD_REQUEST— URL o evento inválidos (HTTPS URL is required,Invalid URL destination,Port not allowed,Host does not resolve,Invalid event: …).
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
}'const res = await fetch('https://api.hegodigitaldev.com/api/v1/buyer/webhook', {
method: 'POST',
headers: {
'X-API-Key': process.env.HEGO_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://tienda.example.com/hooks/hego',
events: ['order.completed', 'order.failed', 'stock.changed', 'balance.low'],
balanceThresholdUsdt: 20,
}),
});
const { webhook } = await res.json();
// El secret solo se muestra ahora: guárdalo en tu gestor de secretos
console.log(webhook.secret);import os, requests
res = requests.post(
'https://api.hegodigitaldev.com/api/v1/buyer/webhook',
headers={'X-API-Key': os.environ['HEGO_API_KEY']},
json={
'url': 'https://tienda.example.com/hooks/hego',
'events': ['order.completed', 'order.failed', 'stock.changed', 'balance.low'],
'balanceThresholdUsdt': 20,
},
timeout=15,
)
# El secret solo se muestra ahora: guárdalo en tu gestor de secretos
print(res.json()['webhook']['secret']){
"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"
}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.
- 400
BAD_REQUEST— faltaconfirm: true. - 404
NOT_FOUND— no hay webhook configurado.
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}'{
"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
| Header | Contenido |
|---|---|
X-Hego-Event | Tipo de evento, por ejemplo order.completed. |
X-Hego-Delivery | ID único del evento (evt_…). Úsalo para deduplicar. |
X-Hego-Signature | Firma: t=<timestamp>,v1=<hmac>. Ver verificación. |
X-Request-Id | Identificador de este envío, para soporte. |
User-Agent | HegoMarket-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#
| Evento | Cuándo | Qué hacer |
|---|---|---|
order.completed | Una 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.failed | Un proveedor no pudo entregar. El importe ya fue devuelto a tu saldo. | Avisa a tu cliente o reintenta con una key nueva. |
catalog.updated | Cambió un producto: precio, nombre, descripción o estado. Puede agrupar varios productos. | Vuelve a leer esos productos con GET /products. |
stock.changed | Cambió el inventario de un producto (reposición, venta, retiro). Puede agrupar varios. | Actualiza tu stock local. |
balance.low | Tu saldo bajó de tu umbral por una compra (se emite una vez al cruzarlo). | Recarga tu billetera. |
{
"orderId": 45013,
"eventId": "evt_9f2b7c1d4a6e08355b2c9d1e7f30a4b6",
"type": "order.completed"
}{
"orderId": 45013,
"eventId": "evt_9f2b7c1d4a6e08355b2c9d1e7f30a4b6",
"type": "order.failed"
}{
"productId": 102,
"productIds": [
98,
102
],
"eventId": "evt_9f2b7c1d4a6e08355b2c9d1e7f30a4b6",
"type": "catalog.updated"
}{
"productId": 102,
"productIds": [
98,
102
],
"eventId": "evt_9f2b7c1d4a6e08355b2c9d1e7f30a4b6",
"type": "stock.changed"
}{
"balanceUsdt": 18.4,
"thresholdUsdt": 20,
"eventId": "evt_9f2b7c1d4a6e08355b2c9d1e7f30a4b6",
"type": "balance.low"
}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.
- Lee
tyv1del headerX-Hego-Signature. - Construye el texto firmado:
t+.+X-Hego-Delivery+.+ cuerpo crudo exacto. - Calcula
HMAC-SHA256con tu secret (whsec_live_…) y compáralo en hexadecimal conv1, en tiempo constante. - 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
});import hashlib, hmac, os, time
from flask import Flask, request, abort
app = Flask(__name__)
TOLERANCE_S = 5 * 60
@app.post('/hooks/hego')
def hego_webhook():
raw = request.get_data() # cuerpo CRUDO, sin re-serializar
parts = dict(p.split('=', 1) for p in request.headers.get('X-Hego-Signature', '').split(','))
event_id = request.headers.get('X-Hego-Delivery', '')
fresh = abs(time.time() * 1000 - int(parts.get('t', 0))) <= TOLERANCE_S * 1000
expected = hmac.new(
os.environ['HEGO_WEBHOOK_SECRET'].encode(),
f"{parts.get('t')}.{event_id}.".encode() + raw,
hashlib.sha256,
).hexdigest()
if not fresh or not hmac.compare_digest(expected, parts.get('v1', '')):
abort(401)
event = request.get_json()
enqueue(event) # deduplica por event["eventId"]
return '', 200Si 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:
# 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
3xxcuenta 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 /productsyGET /orders/{id}.
Códigos de error#
Todos los errores comparten el mismo formato y un error estable sobre el que puedes programar.
| Código | HTTP | Cuándo ocurre | Qué hacer |
|---|---|---|---|
BAD_REQUEST | 400 | Petición mal formada o campos inválidos. | Corrige la petición; no reintentes igual. |
UNAUTHORIZED | 401 | API key ausente, inválida o revocada. | Revisa la key; regenérala si se filtró. |
INSUFFICIENT_BALANCE | 402 | Saldo insuficiente. | Recarga y reintenta con una key nueva. |
FORBIDDEN | 403 | La key no tiene el scope necesario. | Usa una key con ese permiso. |
NOT_FOUND | 404 | Producto, orden, webhook o ruta inexistente. | Verifica el ID. |
PRICE_CHANGED | 409 | El precio total actual no coincide con expectedPriceUsdt. | Usa currentPriceUsdt con una key nueva. |
OUT_OF_STOCK | 409 | Stock insuficiente. | Reintenta más tarde (misma key) o reduce la cantidad. |
IDEMPOTENCY_CONFLICT | 409 | La key ya se usó con otro cuerpo. | Usa una key nueva para una compra distinta. |
IDEMPOTENCY_UNRESOLVED | 409 | Un intento anterior se interrumpió sin cobrarse. | Reintenta con una key nueva. |
DELIVERY_NOT_READY | 409 | Descarga de una orden que aún no está completada. | Espera a completed. |
WEBHOOK_SECRET_UNREADABLE | 409 | El secret guardado no puede leerse. | Registra el webhook de nuevo. |
RATE_LIMITED | 429 | Demasiadas peticiones. | Espera Retry-After segundos. |
INTERNAL_ERROR | 500 | Error inesperado nuestro. | Reintenta con retroceso; reporta el requestId si persiste. |
PURCHASE_FAILED | 502 | La compra no pudo completarse (por ejemplo, falló el proveedor). Tu saldo no se debitó. | Reintenta; si persiste, contacta a soporte con el requestId. |
PROVIDER_TIMEOUT | 504 | El proveedor externo no respondió en 15 s. Tu saldo no se debitó. | Espera unos segundos y reintenta con una key nueva. |
DELIVERY_UNAVAILABLE | 502 / 503 | No se pudo recuperar el archivo. | Reintenta en unos segundos. |
{
"success": false,
"error": "INSUFFICIENT_BALANCE",
"message": "Insufficient wallet balance",
"requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}{
"success": false,
"error": "OUT_OF_STOCK",
"message": "Insufficient stock",
"requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}{
"success": false,
"error": "RATE_LIMITED",
"message": "Rate limit exceeded",
"requestId": "3f0c6a52-8d1b-4a0e-9c47-2b5e7d9a1c30"
}{
"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.
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
orderIdy 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:
- Al arrancar, carga todo con
GET /productsy guárdalo. - Suscríbete a
catalog.updatedystock.changed. - Al recibir un evento, vuelve a leer el catálogo (o solo los
productIdsindicados) y actualiza tu copia. - Como red de seguridad, refresca el catálogo completo cada 10–15 minutos.
- Envía siempre
expectedPriceUsdt: si tu copia quedó vieja, la API te protege conPRICE_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 -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"}}'{
"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_REQUESTy 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-45014import { writeFile } from 'node:fs/promises';
const res = await fetch('https://api.hegodigitaldev.com/api/v1/buyer/orders/45014/download', {
headers: { 'X-API-Key': process.env.HEGO_API_KEY },
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
await writeFile('pedido-45014', Buffer.from(await res.arrayBuffer()));
console.log(res.headers.get('content-type'));import os, requests
res = requests.get(
'https://api.hegodigitaldev.com/api/v1/buyer/orders/45014/download',
headers={'X-API-Key': os.environ['HEGO_API_KEY']},
timeout=60,
)
res.raise_for_status()
open('pedido-45014', 'wb').write(res.content)
print(res.headers['Content-Type'])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ón | Fecha | Cambios |
|---|---|---|
v1 | 2026-09 | Compras 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. |
v1 | 2026-09 | Endurecimiento: 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. |
v1 | 2026-09 | Lanzamiento: 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.