Skip to main content

Webhooks

Steward delivers real-time event notifications to your configured webhook endpoints. Every transaction state change, policy violation, and spend threshold crossing fires a signed HTTP POST to your URL. Base path: /webhooks
Auth: Tenant-level (X-Steward-Key)

Event Types

EventFired when
tx.pendingA transaction is queued for human approval
tx.approvedA pending transaction is approved
tx.deniedA pending transaction is denied
tx.signedA transaction is signed (and optionally broadcast)
spend.thresholdAn agent’s spend tracking threshold is crossed
policy.violationA hard policy rejects a transaction
You can subscribe to any subset of events or omit events to receive all of them.

Register a Webhook

POST /webhooks
curl -X POST https://api.steward.fi/webhooks \
  -H "X-Steward-Key: your-tenant-key" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/steward",
    "events": ["tx.pending", "tx.signed", "policy.violation"],
    "description": "Main webhook for agent notifications",
    "maxRetries": 5,
    "retryBackoffMs": 60000
  }'
Response:
{
  "ok": true,
  "data": {
    "id": "wh_550e8400-...",
    "tenantId": "your-tenant",
    "url": "https://your-app.com/webhooks/steward",
    "events": ["tx.pending", "tx.signed", "policy.violation"],
    "secret": "whsec_a1b2c3d4...",
    "enabled": true,
    "maxRetries": 5,
    "retryBackoffMs": 60000,
    "description": "Main webhook for agent notifications",
    "createdAt": "2026-03-27T00:00:00Z"
  }
}
The secret is only returned on creation. Store it securely — you’ll need it to verify incoming webhook signatures.
Body fields:
FieldRequiredDescription
urlHTTPS endpoint to receive events
eventsArray of event types to subscribe to (default: all)
descriptionHuman-readable label
maxRetriesMax delivery attempts on failure (0–10, default: 5)
retryBackoffMsMilliseconds between retries (min: 1000, default: 60000)

List Webhooks

GET /webhooks
curl https://api.steward.fi/webhooks \
  -H "X-Steward-Key: your-tenant-key"
Returns all webhooks for the tenant. The secret field is omitted from list responses.

Update a Webhook

PUT /webhooks/:id
curl -X PUT https://api.steward.fi/webhooks/wh_550e8400-... \
  -H "X-Steward-Key: your-tenant-key" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": false,
    "events": ["tx.pending", "tx.approved", "tx.denied"]
  }'
Updatable fields: url, events, enabled, description, maxRetries, retryBackoffMs.

Delete a Webhook

DELETE /webhooks/:id
curl -X DELETE https://api.steward.fi/webhooks/wh_550e8400-... \
  -H "X-Steward-Key: your-tenant-key"

Delivery History

GET /webhooks/:id/deliveries?limit=50&offset=0
Returns recent delivery attempts with status, response code, and error details:
{
  "ok": true,
  "data": [
    {
      "id": "del_...",
      "webhookId": "wh_...",
      "event": "tx.signed",
      "status": "delivered",
      "attempts": 1,
      "responseStatus": 200,
      "createdAt": "2026-03-27T12:34:56Z"
    },
    {
      "id": "del_...",
      "event": "tx.pending",
      "status": "failed",
      "attempts": 5,
      "lastError": "Connection refused",
      "nextRetryAt": null
    }
  ]
}

Retry a Failed Delivery

POST /webhooks/deliveries/:id/retry
Resets the delivery to pending with 0 attempts and schedules an immediate re-delivery.

Payload Format

All events share the same envelope:
{
  "id": "evt_abc123",
  "event": "tx.signed",
  "tenantId": "your-tenant",
  "agentId": "agent-1",
  "timestamp": "2026-03-27T12:34:56.789Z",
  "data": { ... }
}
Event-specific data fields:
{
  "approvalId": "appr_...",
  "txId": "tx_...",
  "to": "0xSomeAddress",
  "value": "500000000000000000",
  "chainId": 8453,
  "policyResults": [
    { "policyId": "auto-threshold", "type": "auto-approve-threshold", "passed": false, "reason": "Value exceeds threshold" }
  ]
}
{
  "txId": "tx_...",
  "txHash": "0xabc...",
  "to": "0xSomeAddress",
  "value": "10000000000000000",
  "chainId": 8453,
  "broadcast": true
}
{
  "txId": "tx_...",
  "to": "0xBlockedAddress",
  "value": "1000000000000000000",
  "failedPolicies": [
    { "policyId": "whitelist", "type": "approved-addresses", "passed": false, "reason": "Address not in whitelist" }
  ]
}

Verifying Signatures

Every delivery includes an X-Steward-Signature header. Verify it to ensure the payload came from Steward:
import { createHmac } from "crypto";

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expected = createHmac("sha256", secret)
    .update(payload)
    .digest("hex");
  return `sha256=${expected}` === signature;
}

// Express example
app.post("/webhooks/steward", (req, res) => {
  const sig = req.headers["x-steward-signature"] as string;
  const body = JSON.stringify(req.body);

  if (!verifyWebhookSignature(body, sig, process.env.WEBHOOK_SECRET!)) {
    return res.status(401).send("Invalid signature");
  }

  const { event, agentId, data } = req.body;

  if (event === "tx.pending") {
    // Notify your team to review the pending transaction
    await notifyAdmin(agentId, data.approvalId, data.value);
  }

  res.json({ ok: true });
});

Delivery Guarantees

  • Steward delivers with at-least-once semantics — design your handler to be idempotent
  • Failed deliveries are retried up to maxRetries times with retryBackoffMs spacing
  • Your endpoint should respond 2xx within 10 seconds or the attempt is counted as failed
  • Delivery history is kept for 30 days

  • Approvals — Review and act on tx.pending events
  • Tenant Config — Enable webhookCallbackEnabled in approval config