HProxy/ docs

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).

POST/api/v1/webhooks

Register an endpoint

Register an https URL for event pushes. The response carries your signing 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.
ParameterTypeRequiredDescription
urlstringrequiredHTTPS endpoint to receive signed event POSTs.
eventsstring[]optionalSubset 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"]}'
GET/api/v1/webhooks

List endpoints

Your registered endpoints, newest first. Secrets are never re-shown.
curl https://hproxy.com/api/v1/webhooks -H "X-API-Key: hpx_your_key_here"
DELETE/api/v1/webhooks/{id}

Delete an endpoint

Remove an endpoint; its pending deliveries go with it.
curl -X DELETE https://hproxy.com/api/v1/webhooks/whs_1f7c9a2b8d3e4f50a1b2c3d4e5f60718 \
  -H "X-API-Key: hpx_your_key_here"
POST/api/v1/webhooks/{id}/test

Send a test delivery

Queues a 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.

Delivery body
{
  "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.

plan.low_traffic delivery
{
  "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));
}
Verify before you trust
Parse the body ONLY after the signature checks out, and reject events whose 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.