Webhooks & events

Hbridge delivers signed, at-least-once webhooks to your endpoint as things happen. Set your endpoint URL and get your signing secret in the dashboard under Webhooks — then verify every request and dedupe on the event id.

Event types#

Your endpoint receives these event types:

FieldTypeRequiredDescription
address.provisionedeventnoA deposit address was created for a user on a chain.
deposit.pendingeventnoA deposit was detected but has not yet reached finality.
deposit.confirmedeventnoA deposit reached finality and counts toward your balance.
deposit.reorgedeventnoA previously reported deposit was dropped by a chain reorg.
payout.completedeventnoA payout settled on-chain (net sent to your address).
payout.failedeventnoA payout could not be completed.

Payload#

Every event is a JSON envelope with a stable type, a unique id (dedupe on this), a created_at timestamp, and event-specific fields.

Example: deposit.confirmed
{
  "type": "deposit.confirmed",
  "id": "evt_9f2c...",
  "user": { "id": "6a44...", "external_ref": "user_42" },
  "chain": "ETH",
  "token": "USDC",
  "amount": "2",
  "txid": "0x3646...98f9a",
  "created_at": "2026-07-02T10:07:00.000Z"
}

Verify signatures#

Each request carries an X-Hbridge-Signature header of the form t=<timestamp>,v1=<hmac>. Compute the HMAC-SHA256 of "<t>.<rawBody>" using your signing secret and compare it to v1 with a constant-time comparison. Use the raw request body (not re-serialized JSON).

Header
X-Hbridge-Signature: t=1719914820,v1=9c8f...e3a1
Node.js verification
import crypto from "node:crypto";

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(
    header.split(",").map((kv) => kv.split("=")),
  );
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(parts.v1),
  );
}

Event API#

Every outbound event is also stored, so you can audit what was sent and replay anything your endpoint missed (e.g. during downtime).

GET/v1/events

List outbound events, newest first. Paginated.

Query
FieldTypeRequiredDescription
typestringnoFilter by event type, e.g. deposit.confirmed.
statusstringnoOne of pending, delivered, failed.
limitnumbernoPage size, 1–200, default 50.
cursorstringnoPrevious next_cursor.
200 Response
{
  "events": [
    {
      "id": "6e01...",
      "event_id": "evt_9f2c...",
      "type": "deposit.confirmed",
      "delivery_status": "delivered",
      "attempts": 1,
      "last_attempt_at": "2026-07-02T10:07:03.000Z",
      "delivered_at": "2026-07-02T10:07:03.000Z",
      "created_at": "2026-07-02T10:07:00.000Z"
    }
  ],
  "next_cursor": "6e01...",
  "has_more": true
}
GET/v1/events/:id

Fetch a single event, including the full payload Hbridge sent.

Path
FieldTypeRequiredDescription
idstringyesThe event id (not event_id).
POST/v1/events/:id/redeliver

Re-send an event to your endpoint (even one already delivered). Returns 202; 422 no_webhook_configured if you have no endpoint set.

202 Response
{ "id": "6e01...", "event_id": "evt_9f2c...", "status": "pending" }