GitHub fixerprotocol.org →
Reference

Webhooks

Receive real-time HTTP notifications when payment events occur, policy violations, budget thresholds, confirmed payments, and wallet funding.

ℹ️
Webhooks ship as part of Phase 2 (Q4 2026). This page documents the planned interface ahead of release.

Setting up webhooks

Webhook endpoints are configured in the dashboard at app.fixerprotocol.org → Settings → Webhooks, or via the API:

bash
curl -X POST https://api.fixerprotocol.org/v1/webhooks \
  -H "Authorization: Bearer $FIXER_API_KEY" \
  -H "X-Agent-Id: my-agent" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://yourapp.com/webhooks/fixer",
    "events": [
      "payment.confirmed",
      "policy.violated",
      "budget.threshold",
      "wallet.low_balance"
    ],
    "secret": "your_webhook_signing_secret"
  }'

Fixer Protocol sends a POST request to your URL for each matching event. Your endpoint must respond with HTTP 200 within 10 seconds.

Event types

payment.confirmed

Fired when a payment settles on Solana with confirmed status.

json
{
  "id": "evt_01HZ9QP...",
  "type": "payment.confirmed",
  "created": "2026-05-30T14:22:03Z",
  "agentId": "my-agent",
  "data": {
    "txHash": "5xGh9KmBv...",
    "endpoint": "https://api.dune.com/api/v1/query/3326266/results",
    "amount": { "usdc": 0.002 },
    "protocol": "x402",
    "solanaTxLink": "https://explorer.solana.com/tx/5xGh9KmBv..."
  }
}

policy.violated

Fired when a payment is blocked by a spend policy. The violation is also recorded on-chain.

json
{
  "id": "evt_01HZ9QR...",
  "type": "policy.violated",
  "created": "2026-05-30T14:22:47Z",
  "agentId": "my-agent",
  "data": {
    "rule": "daily_budget",
    "attempted_endpoint": "https://api.proprietary.com/v1/data",
    "attempted_amount": { "usdc": 1.50 },
    "policy_limit": { "usdc": 100 },
    "current_spend": { "usdc": 99.80 },
    "message": "Payment would exceed daily budget of 100 USDC. Current spend: 99.80 USDC."
  }
}

budget.threshold

Fired when daily spend crosses a configured percentage of the daily budget. Configure thresholds at 50%, 75%, and 90% in dashboard settings.

json
{
  "id": "evt_01HZ9QS...",
  "type": "budget.threshold",
  "created": "2026-05-30T13:55:10Z",
  "agentId": "my-agent",
  "data": {
    "threshold_pct": 75,
    "daily_budget": { "usdc": 100 },
    "current_spend": { "usdc": 75.20 },
    "remaining": { "usdc": 24.80 }
  }
}

rate_limit.hit

Fired when a payment is rejected due to the rate limit policy.

json
{
  "id": "evt_01HZ9QT...",
  "type": "rate_limit.hit",
  "created": "2026-05-30T14:01:00Z",
  "agentId": "my-agent",
  "data": {
    "limit": { "calls": 500, "window": "1h" },
    "calls_in_window": 500,
    "window_resets_at": "2026-05-30T15:00:00Z"
  }
}

wallet.low_balance

Fired when the wallet USDC balance drops below a configured threshold. Configure the threshold in dashboard settings (default: $5 USDC).

json
{
  "id": "evt_01HZ9QU...",
  "type": "wallet.low_balance",
  "created": "2026-05-30T14:30:00Z",
  "agentId": "my-agent",
  "data": {
    "balance": { "usdc": 3.42 },
    "threshold": { "usdc": 5.00 },
    "wallet_address": "7xKXtg2eH9sZ7pWb..."
  }
}

wallet.funded

Fired when a wallet funding transaction confirms on Solana.

json
{
  "id": "evt_01HZ9QV...",
  "type": "wallet.funded",
  "created": "2026-05-30T10:00:05Z",
  "agentId": "my-agent",
  "data": {
    "txHash": "7KLMnPqR2sTuWx...",
    "amount": { "usdc": 100 },
    "new_balance": { "usdc": 142.50 }
  }
}

All event types

EventTrigger
payment.confirmedPayment settles on Solana
payment.failedPayment fails after submission (network error, Solana rejection)
policy.violatedPayment blocked by a spend policy rule
budget.thresholdDaily spend crosses 50%, 75%, or 90% of daily budget
rate_limit.hitPayment rejected by rate limit policy
wallet.low_balanceWallet balance drops below configured threshold
wallet.fundedWallet funding transaction confirmed

Signature verification

Every webhook request includes a Fixer-Signature header, an HMAC-SHA256 signature of the raw request body using your webhook secret. Always verify this before processing the event.

typescript
import { createHmac, timingSafeEqual } from "crypto";

const WEBHOOK_SECRET = process.env.FIXER_WEBHOOK_SECRET!;

function verifyWebhook(
  rawBody: string,
  signature: string | null,
): boolean {
  if (!signature) return false;

  const expected = createHmac("sha256", WEBHOOK_SECRET)
    .update(rawBody)
    .digest("hex");

  // Use timing-safe comparison to prevent timing attacks
  return timingSafeEqual(
    Buffer.from(signature, "hex"),
    Buffer.from(expected, "hex"),
  );
}

// Express handler example
app.post("/webhooks/fixer", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.headers["fixer-signature"] as string;
  const body = req.body.toString();

  if (!verifyWebhook(body, sig)) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const event = JSON.parse(body);

  switch (event.type) {
    case "policy.violated":
      console.warn("Policy violation:", event.data.message);
      break;
    case "budget.threshold":
      console.warn(`Budget ${event.data.threshold_pct}% used`);
      sendSlackAlert(event.data);
      break;
    case "wallet.low_balance":
      triggerAutomaticTopUp(event.data.wallet_address);
      break;
  }

  res.status(200).json({ received: true });
});

The Fixer-Signature header format:

text
Fixer-Signature: sha256=<hex-encoded-hmac>

Delivery guarantees

  • At-least-once delivery. Fixer Protocol retries events up to 5 times with exponential backoff (10s, 30s, 2m, 10m, 1h) if your endpoint returns a non-200 response or times out.
  • Ordering. Events within the same agent are delivered in order of occurrence, but retries may cause out-of-order delivery across agents. Use the created timestamp for ordering.
  • Idempotency. Your handler should be idempotent, the same event may be delivered more than once. Use the event id field to deduplicate.
  • Timeout. Your endpoint must respond within 10 seconds. For slow processing, respond immediately and process asynchronously (e.g. queue the event and process in the background).

Testing webhooks

Send a test event from the dashboard or via the API to verify your endpoint is working:

bash
curl -X POST https://api.fixerprotocol.org/v1/webhooks/test \
  -H "Authorization: Bearer $FIXER_API_KEY" \
  -H "X-Agent-Id: my-agent" \
  -H "Content-Type: application/json" \
  -d '{
    "webhookId": "wh_01HZ...",
    "type": "payment.confirmed"
  }'

For local development, use a tool like ngrok to expose your local server to the internet.