> ## Documentation Index
> Fetch the complete documentation index at: https://docs.barker.money/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Subscribe to product events and verify the HMAC-SHA256 signature.

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](https://portal.barker.money/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_type`        | Trigger                                                                      | Confirmation depth                                                                       |
| ------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `deposit.confirmed` | Engine `Deposit` log seen at confirmation depth — shares minted              | per chain (see [Deposit/Redeem Lifecycle](/deposit-redeem-lifecycle#confirmation-depth)) |
| `redeem.requested`  | Engine `RedeemRequested` log — **async vaults only** (no synchronous redeem) | same                                                                                     |
| `redeem.confirmed`  | Engine `Withdraw` log — shares burnt, asset transferred                      | same                                                                                     |

Payload (`deposit.confirmed` / `redeem.confirmed`):

```json theme={null}
{
  "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):

```json theme={null}
{
  "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](/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_type`  | Trigger                                                              | Payload fields                                              |
| ------------- | -------------------------------------------------------------------- | ----------------------------------------------------------- |
| `apy_change`  | Net APY moved by more than ±10% day-over-day on one of your products | `slug`, `previous_apy`, `current_apy`, `change_pct`, `date` |
| `tvl_alert`   | Engine total assets moved by more than ±20% day-over-day             | `slug`, `previous_tvl`, `current_tvl`, `change_pct`, `date` |
| `vault_pause` | A product was set to `paused` (manual ops or upstream vault halt)    | `slug`, `status`                                            |

## Request envelope

```http theme={null}
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`, `status` ∈ `pending` / `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).

<CodeGroup>
  ```js Node.js (Express) theme={null}
  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);
    },
  );
  ```

  ```python Python (FastAPI) theme={null}
  import hmac
  import hashlib
  import os
  from fastapi import FastAPI, Header, HTTPException, Request

  app = FastAPI()
  SECRET = os.environ["BARKER_WEBHOOK_SECRET"].encode()

  @app.post("/webhooks/barker")
  async def barker_webhook(
      request: Request,
      x_barker_signature: str = Header(...),
      x_barker_event: str = Header(...),
  ):
      body = await request.body()
      expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
      if not hmac.compare_digest(expected, x_barker_signature):
          raise HTTPException(401, "invalid signature")
      event = await request.json()
      handle_event(x_barker_event, event)
      return {"ok": True}
  ```

  ```go Go (net/http) theme={null}
  package main

  import (
      "crypto/hmac"
      "crypto/sha256"
      "encoding/hex"
      "io"
      "net/http"
      "os"
  )

  func barkerWebhook(w http.ResponseWriter, r *http.Request) {
      body, _ := io.ReadAll(r.Body)
      sig := r.Header.Get("X-Barker-Signature")

      mac := hmac.New(sha256.New, []byte(os.Getenv("BARKER_WEBHOOK_SECRET")))
      mac.Write(body)
      expected := hex.EncodeToString(mac.Sum(nil))

      if !hmac.Equal([]byte(expected), []byte(sig)) {
          http.Error(w, "invalid signature", http.StatusUnauthorized)
          return
      }
      eventType := r.Header.Get("X-Barker-Event")
      handleEvent(eventType, body)
      w.WriteHeader(http.StatusOK)
  }
  ```

  ```bash cURL (compute signature locally for debugging) theme={null}
  SECRET="your_webhook_secret"
  BODY='{"slug":"acme-usdc-base","current_apy":0.048}'
  echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex
  ```
</CodeGroup>

## Idempotency

Network failures + retries mean you may receive the same delivery more than once. Dedupe key depends on the event family:

| `event_type`                                                  | Dedupe key                                   | Notes                                                                                |
| ------------------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------------ |
| `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 <a href="mailto:partners@barker.money">[partners@barker.money](mailto:partners@barker.money)</a>.

For the failure handling that pairs with these guarantees — RPC outages, chain reorgs, partner-side endpoint downtime — see [Failure modes](/failure-modes).

## Configure in Portal

1. Open [Portal → Webhooks](https://portal.barker.money/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 <a href="https://webhook.site" target="_blank" rel="noreferrer">webhook.site</a> 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.
