Integrations & webhooks — connect StaticOwl to other systems
StaticOwl has three ways to wire your site to the systems around it:
| Direction | Use it for | |
|---|---|---|
| Integrations | Outbound | A simple "when X happens, post to Slack / Discord / a webhook" rule. |
| Outbound webhooks | Outbound | Signed, retried event delivery to your own endpoint. |
| Inbound webhooks | Inbound | Let an external system POST events into your site. |
All three are site-scoped and admin-gated. Outbound delivery is best-effort — a failing endpoint is counted, never allowed to break a publish or a visitor's request.
Integrations — "when X happens, do Y"
An integration binds a trigger (a site event) to a destination (Slack, Discord, or a generic webhook) with a URL and an optional message template. When the event fires, StaticOwl posts a formatted message to the destination.
Triggers
An integration can listen for any of these events:
feedback.createdform.submittedcomment.creatednewsletter.subscribeddsar.submitted,dsar.completed— data-subject access requestsdata.deleted— right-to-be-forgotten erasureconsent.withdrawn,consent.config_changed
Destinations
- slack — posts
{ "text": "…" }to a Slack incoming-webhook URL. - discord — posts
{ "content": "…" }to a Discord webhook URL. - webhook — posts a fuller JSON body:
{ event, site, at, text, data }, wheredatais the event payload.
The message text is a default summary of the event, or — if you set a template —
your own string with {{field}} placeholders filled from the event ({{event}}
plus the payload fields). The destination URL must be https://.
Create one
curl -X POST https://app.staticowl.com/api/integrations \
-H "Authorization: Bearer $STATICOWL_API_KEY" \
-H "X-Site-Id: site:mysite" \
-H "Content-Type: application/json" \
-d '{"type":"slack","trigger":"form.submitted","url":"https://hooks.slack.com/…",
"template":"New submission on {{page}}: {{email}}"}'
GET /api/integrations/meta returns the valid triggers and types. Posting the
same rule again (same id) overwrites it.
Test and verify
- Before you save —
POST /api/integrations/test-fireposts a sample to an ad-hoc URL so you can confirm the endpoint works before persisting anything. - After you save —
POST /api/integrations/{id}/testfires a synthetic event through the saved rule. - Confirm real events land —
GET /api/integrations/{id}/deliveriesreturns the recent delivery log (newest first, up to 25), with the HTTP status, any error, and the timing of each attempt. This is how you tell whether a real event — not just the test — reached its destination.
API and MCP reference
| Route | MCP tool |
|---|---|
GET /api/integrations/meta |
integrations_list_types |
GET /api/integrations |
integrations_list |
POST /api/integrations |
integrations_create |
PUT /api/integrations/{id}/enabled |
integrations_toggle |
DELETE /api/integrations/{id} |
integrations_delete |
POST /api/integrations/{id}/test |
integrations_test |
GET /api/integrations/{id}/deliveries |
integrations_deliveries |
Outbound webhooks
An outbound webhook is a raw, signed event feed to your own endpoint. Where an integration formats a message for Slack or Discord, a webhook delivers the event as JSON and lets you decide what to do with it — and it is retried on failure.
Subscribe
curl -X POST https://app.staticowl.com/api/webhooks \
-H "Authorization: Bearer $STATICOWL_API_KEY" \
-H "X-Site-Id: site:mysite" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/hook","events":["content.published","site.deployed"],"secret":"my-shared-secret"}'
eventsis a list of event names, or omit it (or pass["*"]) to receive all events.secretis optional. If you don't supply one, StaticOwl generates a secret for the subscription so deliveries are always signed.
Events StaticOwl fires include: content.published, content.unpublished,
deployment.deployed, deployment.rolled_back, site.deployed,
feedback.created, newsletter.subscribed, comment.created,
consent.withdrawn, consent.config_changed, dsar.submitted,
dsar.completed, data.deleted, and form.submitted. Subscribe to ["*"] if
you want to receive new event types as they're added.
The delivery
Each delivery is a POST with a JSON body:
{
"event": "content.published",
"siteId": "site:mysite",
"at": "2026-10-16T12:00:00.000Z",
"data": { "...": "event-specific fields" }
}
with two headers:
x-staticowl-event— the event name.x-staticowl-signature—sha256=<hex>, an HMAC-SHA256 of the raw request body keyed with your secret. Recompute it on your side over the exact bytes you received and compare to verify the delivery came from StaticOwl.
Retries
Delivery is retried with exponential backoff (plus a little jitter) on transient failures — a network error, a timeout, HTTP 429, or any 5xx. A non-2xx 4xx (the receiver rejected the payload) is treated as permanent and not retried. Dispatch runs detached from the request that triggered it, so retries never delay a visitor.
API and MCP reference
| Route | MCP tool |
|---|---|
GET /api/webhooks |
webhooks_list |
POST /api/webhooks |
webhook_create |
DELETE /api/webhooks/{id} |
webhook_delete |
Inbound webhooks
An inbound webhook is a receiver external systems POST into — the reverse of an outbound webhook. Use it to push events from another tool into your site.
Create a receiver
curl -X POST https://app.staticowl.com/api/webhooks/inbound \
-H "Authorization: Bearer $STATICOWL_API_KEY" \
-H "X-Site-Id: site:mysite" \
-H "Content-Type: application/json" \
-d '{"name":"Orders from Shopify","slug":"shopify-orders"}'
The response includes the signing secret — shown once, at creation. Record
it now; it isn't retrievable later. The slug is lowercase letters, digits, and
hyphens (1–42 characters) and becomes part of the public receiver URL.
The public URL and signing
External systems POST to:
POST https://app.staticowl.com/api/hooks/<siteId>/<slug>
There's no API key or token on this URL — the signature is the authentication. The caller signs the raw request body with HMAC-SHA256 using the receiver's secret and sends it as:
X-Webhook-Signature: sha256=<hex>
The receiver verifies the signature against the exact body bytes and responds:
- 202 — signature valid; the event is stored.
- 401 — signature invalid. The event is still recorded (so you can see the rejected attempt) but not counted as a valid delivery.
- 404 — no such site or receiver.
Every event is stored with its payload (up to 200 KB) and the X-* and
content-type request headers, so you can inspect exactly what arrived.
Manage, inspect, and replay
GET /api/webhooks/inbound— list receivers (without secrets).POST /api/webhooks/inbound/{id}/rotate-secret— mint a new secret (invalidates the old one; returned once).POST /api/webhooks/inbound/{id}/enabled— enable or disable without rotating.GET /api/webhooks/inbound/{id}/events— recent events for a receiver, newest first, each with its payload and whether the signature was valid.POST /api/webhooks/inbound/events/{eventId}/replay— re-record an event under a fresh id (useful for inspection and re-processing from your own side).
API and MCP reference
| Route | MCP tool |
|---|---|
GET /api/webhooks/inbound |
webhook_inbound_list |
POST /api/webhooks/inbound |
webhook_inbound_create |
DELETE /api/webhooks/inbound/{id} |
webhook_inbound_delete |
POST /api/webhooks/inbound/{id}/rotate-secret |
webhook_inbound_rotate_secret |
GET /api/webhooks/inbound/{id}/events |
webhook_inbound_events |
POST /api/webhooks/inbound/events/{eventId}/replay |
webhook_inbound_replay |
See also
- Forms — collect submissions from any page — form submissions
can fire their own notification webhook, and also raise the
form.submittedevent. - Distribution — web push & scheduled social posts — the bring-your-own-webhook model for social posting.
- Compliance — the DSAR and erasure events you can route to your downstream systems.