Webhooks
Register an HTTPS endpoint and get pushed order.fulfilled, order.failed, order.refunded and subscription events, HMAC-SHA256 signed, with retries — instead of polling.
Stop polling. Register an HTTPS endpoint and we push you a signed JSON event the moment an order or subscription reaches a terminal state. Deliveries are HMAC-SHA256 signed, retried with backoff for hours, and idempotently enqueued (one event per endpoint per resource) — treat them as at-least-once and de-duplicate on (event, data.orderId).
/api/v1/webhooksRegister an endpoint
secret once — store it now, it is never shown again. events defaults to everything; up to 10 active endpoints per account. Private/internal hosts are refused at registration.| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | required | HTTPS endpoint to receive signed event POSTs. |
events | string[] | optional | Subset of: order.fulfilled, order.failed, order.refunded, subscription.canceled, subscription.expired, plan.low_traffic. Omitted = all. |
curl -X POST https://hproxy.com/api/v1/webhooks \
-H "X-API-Key: hpx_your_key_here" \
-H "Content-Type: application/json" \
-d '{"url": "https://api.your-shop.com/hproxy-webhook", "events": ["order.fulfilled", "order.failed"]}'/api/v1/webhooksList endpoints
curl https://hproxy.com/api/v1/webhooks -H "X-API-Key: hpx_your_key_here"/api/v1/webhooks/{id}Delete an endpoint
curl -X DELETE https://hproxy.com/api/v1/webhooks/whs_1f7c9a2b8d3e4f50a1b2c3d4e5f60718 \
-H "X-API-Key: hpx_your_key_here"/api/v1/webhooks/{id}/testSend a test delivery
ping event through the real pipeline — same signing, same worker — so you can verify your endpoint and signature code end to end. Expect the POST within about a minute.curl -X POST https://hproxy.com/api/v1/webhooks/whs_1f7c9a2b8d3e4f50a1b2c3d4e5f60718/test \
-H "X-API-Key: hpx_your_key_here"What a delivery looks like
Every delivery is a POST with Content-Type: application/json and three headers: X-HProxy-Event (the event type), X-HProxy-Signature (hex HMAC-SHA256 of the raw body) and X-HProxy-Timestamp (a convenience mirror — the tamper-proof time is created inside the signed body). Order events carry the same public vocabulary as GET /orders/{id}, so your handler and your poller read identical statuses.
{
"event": "order.fulfilled",
"created": "2026-07-23T09:16:41.220Z",
"resource": "order",
"data": {
"orderId": "ord_8f491789-58cd-4e82-81a2-ec7c28088821",
"status": "completed",
"productId": "ipv4",
"quantity": 5,
"amountCents": 1050,
"currency": "USD",
"createdAt": "2026-07-23T09:15:12.003Z",
"fulfilledAt": "2026-07-23T09:16:38.551Z"
}
}Plan events: know when a customer runs low
plan.low_traffic fires when an active GB plan drops to 10% or less of its total — the signal a reseller uses to prompt their customer to top up. It carries the plan snapshot including your label, so the alert is already attributed to your customer. It fires once per plan per endpoint (a top-up does not re-arm it), and a newly registered endpoint receives one event per plan that is already low at that moment.
{
"event": "plan.low_traffic",
"created": "2026-07-23T02:01:00.412Z",
"resource": "plan",
"data": {
"planId": "cmjfmhhpd004c4i4hqsk3mzrr",
"label": "customer-1042",
"product": "RESIDENTIAL_PREMIUM",
"dataGbTotal": 10.0,
"dataGbRemaining": 0.8,
"thresholdPct": 10,
"expiresAt": null
}
}Verifying the signature
Compute HMAC-SHA256 of the exact raw request body bytes with your whsec_… secret and compare it (constant-time) to X-HProxy-Signature. We sign the exact bytes we send, so there is nothing to re-serialize. Respond 2xx to acknowledge; anything else is retried with exponential backoff (first retry ~30s, doubling, capped at 6h) for up to 10 attempts.
import crypto from "crypto";
export function verifyHproxySignature(rawBody, signatureHeader, secret) {
const expected = crypto.createHmac("sha256", secret)
.update(rawBody) // the EXACT raw bytes, before any JSON.parse
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}created is older than your replay window (5 minutes is a good default).Need a hand wiring this up? Email [email protected]. A real person reads every message.