Browse docs

Webhooks

Webhooks push events to your endpoint as they happen, so downstream systems can react without polling.

Event types

EventFires when
journal_entry.createdA journal entry is created (including drafts).
journal_entry.postedAn entry is posted to the ledger.
journal_entry.reversedA posted entry is reversed.
account.createdAn account is added to the chart.
account.updatedAn account is updated.
period.closedAn accounting period is closed.
period.reopenedA closed period is reopened.

Registering an endpoint

Create a webhook with the URL to call, an optional list of event_types to filter on (omit to receive all), and a secret used to sign deliveries:

bash
curl -X POST "$FLYWHEEL_API/webhooks" \
-H "Authorization: Bearer $FLYWHEEL_KEY" \
-H "Content-Type: application/json" \
-d '{
  "url": "https://your-app.example/hooks/flywheel",
  "event_types": ["journal_entry.posted", "period.closed"],
  "secret": "whsec_your_signing_secret"
}'

Manage endpoints with GET/POST /webhooks and GET/PATCH/DELETE /webhooks/{id}. MCP tools: list_webhooks, create_webhook, get_webhook, update_webhook, delete_webhook.

Delivery headers

Each delivery is an HTTP POST with a JSON body and these headers:

HeaderValue
X-Webhook-EventThe event type, e.g. journal_entry.posted.
X-Webhook-Signaturesha256=<hex> — HMAC-SHA256 of the raw body, keyed by your secret.

Verifying the signature

Always verify the signature before trusting a payload. Compute the HMAC-SHA256 of the raw request body with your endpoint's secret and compare:

ts
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody: string, header: string, secret: string): boolean {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(header);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}

Use the raw body

Verify against the exact bytes received, before any JSON parsing or re-serialization — re-stringifying can change the bytes and break the signature.

Retries

Deliveries that fail are retried automatically by a background worker, so a brief outage on your side won't drop events. Respond with a 2xx status to acknowledge receipt as quickly as possible.