Skip to main content
Barker delivers product events to your endpoint as POST requests with a JSON body and an HMAC-SHA256 signature. Subscribe and inspect delivery logs in Portal → Webhooks.

Event types

Two categories — lifecycle events fire when a chain log appears at confirmation depth (one event per log), operational events fire from the daily metrics processor (one per product per day at most). We add new event types over time. Treat unknown event_type values as a no-op — don’t reject them.

Lifecycle events (tx-driven)

event_typeTriggerConfirmation depth
deposit.confirmedEngine Deposit log seen at confirmation depth — shares mintedper chain (see Deposit/Redeem Lifecycle)
redeem.requestedEngine RedeemRequested log — async vaults only (no synchronous redeem)same
redeem.confirmedEngine Withdraw log — shares burnt, asset transferredsame
Payload (deposit.confirmed / redeem.confirmed):
{
  "slug": "acme-usdc-base",
  "chain_id": 8453,
  "tx_hash": "0xa1b2c3d4...",
  "block_number": 30142876,
  "log_index": 15,
  "user_address": "0x1234567890123456789012345678901234567890",
  "amount": "10000.000000",
  "shares": "9876.123456789012345678",
  "asset_symbol": "USDC",
  "occurred_at": "2026-05-09T12:34:56Z"
}
Payload (redeem.requested — async vaults only):
{
  "slug": "acme-eth-mainnet",
  "chain_id": 1,
  "tx_hash": "0xa1b2c3d4...",
  "log_index": 8,
  "user_address": "0x1234567890123456789012345678901234567890",
  "shares": "5000.0",
  "request_id": "0xdeadbeef...",
  "estimated_settlement_at": "2026-05-09T18:00:00Z",
  "occurred_at": "2026-05-09T12:34:56Z"
}
See Deposit/Redeem Lifecycle for the full state diagram, sync vs async sequencing, and how to reconcile with the /position endpoint.

Operational events (daily aggregate)

event_typeTriggerPayload fields
apy_changeNet APY moved by more than ±10% day-over-day on one of your productsslug, previous_apy, current_apy, change_pct, date
tvl_alertEngine total assets moved by more than ±20% day-over-dayslug, previous_tvl, current_tvl, change_pct, date
vault_pauseA product was set to paused (manual ops or upstream vault halt)slug, status

Request envelope

POST /your/webhook/endpoint HTTP/1.1
Host: yourcompany.com
Content-Type: application/json
X-Barker-Event: apy_change
X-Barker-Signature: 9c3a…f7b1     <- HMAC-SHA256(secret, body), hex

{
  "slug": "acme-usdc-base",
  "previous_apy": 0.0612,
  "current_apy": 0.0480,
  "change_pct": -21.57,
  "date": "2026-05-04"
}
Headers:
  • X-Barker-Event — same as event_type in the table above.
  • X-Barker-Signature — hex HMAC-SHA256 of the raw request body with your webhook secret. Always verify this before trusting the payload.
We expect a 2xx response within 10 seconds. Any other status (or a timeout) counts as a failure.

Retries

Failed deliveries are retried up to 3 attempts total (the original try plus 2 retries). The full attempt history and last response code is visible in the Portal under each delivery row (attempts, statuspending / delivered / failed, response code, response body excerpt). If your endpoint is down longer than the retry budget, the event row is durably stored — you can manually replay from the Portal once you’re back up.

Verifying the signature

The signature is HMAC-SHA256 over the exact bytes of the request body, hex-encoded. Verify on the raw bytes, not after JSON.parse + re-stringify — once you re-serialize, byte equivalence breaks (key order, whitespace, number formatting all differ across runtimes).
import crypto from "node:crypto";
import express from "express";

const app = express();

// Capture the raw body, not parsed JSON
app.post(
  "/webhooks/barker",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.header("x-barker-signature");
    const expected = crypto
      .createHmac("sha256", process.env.BARKER_WEBHOOK_SECRET)
      .update(req.body)              // Buffer, raw
      .digest("hex");

    // Constant-time comparison to prevent timing attacks
    if (
      !signature ||
      signature.length !== expected.length ||
      !crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expected),
      )
    ) {
      return res.status(401).send("invalid signature");
    }

    const event = JSON.parse(req.body.toString("utf8"));
    const eventType = req.header("x-barker-event");
    handleEvent(eventType, event);
    res.sendStatus(200);
  },
);

Idempotency

Network failures + retries mean you may receive the same delivery more than once. Dedupe key depends on the event family:
event_typeDedupe keyNotes
deposit.confirmed / redeem.confirmed / redeem.requested(event_type, chain_id, tx_hash, log_index)At most one delivery per chain log. Persist these four fields and reject duplicates.
apy_change / tvl_alert(event_type, slug, date)At most one per product per day.
vault_pause(event_type, slug)Re-emitted while status remains paused.
A header-based idempotency key is on the roadmap. If you have a use case that needs it sooner, email partners@barker.money. For the failure handling that pairs with these guarantees — RPC outages, chain reorgs, partner-side endpoint downtime — see Failure modes.

Configure in Portal

  1. Open Portal → Webhooks
  2. Add a destination URL (HTTPS only — we won’t deliver to plain HTTP)
  3. Pick the events you want
  4. Copy the secret that appears once on creation. We hash it after that — there’s no way to retrieve it later, only rotate.
While integrating, point the URL at webhook.site first to inspect the exact bytes and headers we send. Move to your real handler after you’re confident the signature verification is correct.

What about inbound webhooks?

Most partner integrations only need outbound events — we tell you when something changes on the chain or product side, and you react. If you have a hybrid flow where your system needs to push state into Barker (e.g. KYC tier promotion, off-chain reconciliation confirmation), there’s an authenticated POST /api/partner/webhooks/inbound endpoint — see the API Reference. Email us first if you think you need it; usually a normal API call is the simpler answer.