Desarrolladores
Webhooks
En esta página
Configurar un endpoint
- Agrega a tu servidor una ruta https que lea el cuerpo sin procesar y responda
2xxen menos de 10 segundos. Haz el trabajo después de responder. - Crea el endpoint con la API, o pídeselo a nuestro equipo. Guarda el secreto
whsec_que devuelve: se muestra una sola vez. - Verifica cada solicitud (ver abajo) y luego elimina duplicados por el
iddel evento. - Envíate un evento de prueba de cada tipo que gestionas.
El evento
Todos los eventos tienen el mismo sobre. data contiene los campos propios del tipo.
idstring- Empieza por evt_. Es el mismo en cada reintento, y en el encabezado webhook-id.
typestring- Uno de los eventos siguientes.
api_versionstring- La versión en que se escribe data: la de tu endpoint.
created_atstring- Cuándo ocurrió, que puede ser antes de que lo recibas.
testboolean- true para eventos de prueba.
dataobject- Ids con los que obtener el objeto completo, y qué cambió.
POST /hooks/ninjabuy HTTP/1.1
Host: acme-goods.com
Content-Type: application/json
User-Agent: NinjaBuy-Webhooks/1.0
webhook-id: evt_4m2x
webhook-timestamp: 1793685600
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{
"id": "evt_4m2x",
"type": "order.shipped",
"api_version": "2026-11",
"created_at": "2026-11-03T06:00:00Z",
"test": false,
"data": {
"order": "ord_3n7q",
"external_id": "1012",
"shipment": "shp_5r8w",
"carrier": "YunExpress",
"tracking_number": "YT2630812345678",
"tracking_url": "https://t.17track.net/en#nums=YT2630812345678",
"shipping_charged": { "amount": "134.52", "currency": "CNY" },
"pushed_to_channel": true
}
}Eventos
order.created
Nos llegó un pedido, de tu tienda de Shopify o de la API. Pagado, o awaiting_payment.
{
"id": "evt_1a7c",
"type": "order.created",
"api_version": "2026-11",
"created_at": "2026-11-01T09:14:02Z",
"test": false,
"data": {
"order": "ord_3n7q",
"external_id": "1012",
"source": "shopify",
"status": "buying",
"payment_status": "paid",
"lines": [
{
"line": "lin_9a2c",
"variant": "var_41hz",
"sku": "5187209554",
"quantity": 1
}
]
}
}order.paid
Se pagó un pedido desde tu saldo. También se dispara para pedidos que esperaban una recarga.
{
"id": "evt_1a9f",
"type": "order.paid",
"api_version": "2026-11",
"created_at": "2026-11-01T11:02:40Z",
"test": false,
"data": {
"order": "ord_3n8a",
"external_id": "1013",
"amount": { "amount": "57.00", "currency": "CNY" },
"paid_from": "balance",
"was_awaiting_payment": true
}
}order.bought
Pagamos al vendedor. Las líneas están en with_seller hasta que llegan a nuestro almacén.
{
"id": "evt_2c4e",
"type": "order.bought",
"api_version": "2026-11",
"created_at": "2026-11-01T13:30:05Z",
"test": false,
"data": {
"order": "ord_3n7q",
"external_id": "1012",
"lines": [
{
"line": "lin_9a2c",
"sku": "5187209554",
"quantity": 1,
"status": "with_seller"
}
]
}
}order.checked
Los artículos están en nuestro almacén, revisados, con sus fotos. Lo siguiente es el empaque.
{
"id": "evt_9w1c",
"type": "order.checked",
"api_version": "2026-11",
"created_at": "2026-11-02T03:20:11Z",
"test": false,
"data": {
"order": "ord_3n7q",
"external_id": "1012",
"fulfillment_order": "fo_7k2m",
"lines": [
{
"line": "lin_9a2c",
"qc": "passed",
"photos": [
"https://media.ninjabuy.com/qc/ord_3n7q/1.webp",
"https://media.ninjabuy.com/qc/ord_3n7q/2.webp"
]
}
]
}
}order.shipped
Un paquete salió con seguimiento; el cargo de envío es definitivo. La tienda de la que vino el pedido también tiene el seguimiento.
{
"id": "evt_4m2x",
"type": "order.shipped",
"api_version": "2026-11",
"created_at": "2026-11-03T06:00:00Z",
"test": false,
"data": {
"order": "ord_3n7q",
"external_id": "1012",
"shipment": "shp_5r8w",
"carrier": "YunExpress",
"tracking_number": "YT2630812345678",
"tracking_url": "https://t.17track.net/en#nums=YT2630812345678",
"shipping_charged": { "amount": "134.52", "currency": "CNY" },
"pushed_to_channel": true
}
}product.cost_changed
Un vendedor cambió un precio, y lo que hizo tu regla de sincronización: recalcular el precio, pausar o notificar.
{
"id": "evt_8q0d",
"type": "product.cost_changed",
"api_version": "2026-11",
"created_at": "2026-11-04T02:10:00Z",
"test": false,
"data": {
"product": "prd_1x0a",
"variant": "var_2b7k",
"sku": "4410923871",
"cost": {
"from": { "amount": "42.00", "currency": "CNY" },
"to": { "amount": "48.00", "currency": "CNY" }
},
"action": "repriced",
"price": {
"from": { "amount": "18.99", "currency": "USD" },
"to": { "amount": "20.99", "currency": "USD" }
},
"rule": "rule_default"
}
}product.out_of_stock
El vendedor se quedó sin una variante y tu estante no tiene ninguna: la ponemos a 0 en tus canales.
{
"id": "evt_2b9n",
"type": "product.out_of_stock",
"api_version": "2026-11",
"created_at": "2026-11-04T02:11:30Z",
"test": false,
"data": {
"product": "prd_8f2k",
"variant": "var_41j0",
"sku": "5187209555",
"warehouse_on_hand": 0,
"channels_updated": [
{
"channel": "shopify",
"shop": "acme-goods.myshopify.com",
"available": 0
}
]
}
}qc.issue
Nuestra revisión encontró un problema con un artículo: color o talla incorrectos, daños, una pieza faltante. Elige antes de respond_by; si no respondes, lo retenemos y te escribimos.
{
"id": "evt_6c3h",
"type": "qc.issue",
"api_version": "2026-11",
"created_at": "2026-11-02T03:20:11Z",
"test": false,
"data": {
"order": "ord_3n7q",
"external_id": "1012",
"fulfillment_order": "fo_7k2m",
"line": "lin_9a2c",
"sku": "5187209554",
"issue": "wrong_color",
"note": "Received grey, ordered black.",
"photos": ["https://media.ninjabuy.com/qc/ord_3n7q/3.webp"],
"choices": ["ship_anyway", "exchange", "refund"],
"respond_by": "2026-11-05T03:20:11Z"
}
}Firmas
Las entregas se firman según Standard Webhooks, así que una biblioteca que quizá ya usas las verifica. Cada entrega lleva tres encabezados:
| Encabezado | Qué contiene |
|---|---|
| webhook-id | El id del evento. |
| webhook-timestamp | Cuándo enviamos este intento, en segundos Unix. |
| webhook-signature | v1, luego una coma y el HMAC-SHA256 en base64 de id.timestamp.body con tu secreto. Durante una rotación, dos de ellos, separados por un espacio. |
La clave es tu secreto tras whsec_, decodificado en base64. Firma el cuerpo sin procesar exactamente como llegó; rechaza una marca de tiempo desfasada más de cinco minutos, para que no se pueda repetir una solicitud capturada.
Verificar una entrega
Con la biblioteca standardwebhooks, o en unas pocas líneas propias:
Reintentos y orden
Cualquier cosa que no sea 2xx en 10 segundos es un fallo, y lo reintentamos, cada vez con el mismo webhook-id y una nueva marca de tiempo y firma:
| Intento | Después del anterior |
|---|---|
| 1 | De inmediato |
| 2 | 5 segundos |
| 3 | 5 minutos |
| 4 | 30 minutos |
| 5 | 2 horas |
| 6 | 5 horas |
| 7 | 10 horas |
| 8 | 10 horas, el último |
- Tras el último intento, la entrega queda fallida. Vuelve a enviarla desde la lista de entregas.
- Un endpoint cuyas entregas fallaron todas durante cinco días se desactiva, tras enviarte un correo. Al activarlo de nuevo se envía lo que se perdió en los últimos tres días.
- Los eventos pueden llegar dos veces y desordenados. Elimina duplicados por id y, cuando el orden importe, obtén el objeto y fíate de su estado en lugar del del evento.