Webhooks
Webhooks push events to your endpoint as they happen, so downstream systems can react without polling.
Event types
| Event | Fires when |
|---|---|
journal_entry.created | A journal entry is created (including drafts). |
journal_entry.posted | An entry is posted to the ledger. |
journal_entry.reversed | A posted entry is reversed. |
account.created | An account is added to the chart. |
account.updated | An account is updated. |
period.closed | An accounting period is closed. |
period.reopened | A 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:
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:
| Header | Value |
|---|---|
X-Webhook-Event | The event type, e.g. journal_entry.posted. |
X-Webhook-Signature | sha256=<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:
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.