Push events to your stack.
Subscribe a URL to Storylayer events. Every delivery is HMAC-signed, retried with exponential backoff, and viewable in the dashboard.
Events
Five event types are published today. Subscribe to specific events or use "*" to fan out everything.
| Event | When |
|---|---|
story.scheduled | A story has a confirmed publish time. |
story.published | A story shipped successfully on at least one channel. |
story.failed | A scheduled story errored at publish time. |
moment.detected | A detector fired against your data. |
moment.auto_drafted | A high-severity moment turned into a draft story. |
Payload shape
All events share an envelope; the data field carries event-specific content.
POST <your-url>
content-type: application/json
x-storylayer-event: story.published
x-storylayer-event-id: evt_2T...
x-storylayer-timestamp: 1745948123
x-storylayer-signature: <hex>
{
"id": "evt_2T...",
"event": "story.published",
"created_at": "2026-04-30T...",
"data": {
"story": {
"id": "...",
"title": "Snow report — 14 inches",
"published_at": "2026-04-30T...",
"published_channels": ["instagram","facebook"]
}
}
}Signing
Each request carries an x-storylayer-signature header — a hex-encoded HMAC-SHA256 of the raw request body, keyed by the signing_secret returned when you create the endpoint. Always verify in constant time.
import crypto from "node:crypto";
const sig = req.headers["x-storylayer-signature"];
const expected = crypto
.createHmac("sha256", SIGNING_SECRET) // whsec_...
.update(rawBody) // raw bytes, NOT JSON.stringify(parsed)
.digest("hex");
const ok = crypto.timingSafeEqual(
Buffer.from(sig, "hex"),
Buffer.from(expected, "hex"),
);
if (!ok) return res.status(401).end();The signing_secret is shown once when you create an endpoint via POST /api/v1/webhooks. Store it securely; we never return it again. Use the dashboard or PATCH /api/v1/webhooks/:id to rotate it.
Retries
If your endpoint returns anything other than a 2xx within 10 seconds, we retry on this schedule:
- 1 minute
- 5 minutes
- 15 minutes
- 1 hour
- 4 hours
- 12 hours
After the sixth failure the delivery is marked permanent_failure and the endpoint's failure_count increments.
Health, alerts, and auto-disable
Each endpoint carries a health_status that reflects how the receiver is doing:
healthy— most recent delivery succeeded.unhealthy— 3 consecutive deliveries failed; an in-app alert is raised.auto_disabled— 10 consecutive deliveries failed; the endpoint is deactivated to avoid drowning your queue. You re-enable it once the receiver is back via the dashboard orPOST /api/v1/webhooks/:id/reactivate.
The first successful delivery after a failure run resets the counter and clears the unhealthy state.
Replay
If your receiver was down for a window, you don't need to ask us to resend events — you can replay them yourself.
# single replay
curl -X POST https://app.storylayer.ai/api/v1/webhooks/wh_.../deliveries/dlv_.../replay \
-H "Authorization: Bearer sl_pat_..."
# bulk replay (failed deliveries in a window)
curl -X POST https://app.storylayer.ai/api/v1/webhooks/wh_.../deliveries/replay \
-H "Authorization: Bearer sl_pat_..." \
-H "content-type: application/json" \
-d '{
"since": "2026-04-30T14:00:00Z",
"until": "2026-04-30T16:00:00Z",
"status": ["failed","permanent_failure"]
}'Replays are inserted as new deliveries with replay_of set to the original delivery id, so audit history is preserved.
Managing endpoints
From the dashboard at /dashboard/developers you can create endpoints, send a signed test ping, and inspect every delivery — including filtering by status and replaying individual or bulk deliveries with one click.
Or via the API:
# create
curl -X POST https://app.storylayer.ai/api/v1/webhooks \
-H "Authorization: Bearer sl_pat_..." \
-H "content-type: application/json" \
-d '{
"url": "https://hooks.acme.example/storylayer",
"events": ["story.published","story.failed","moment.detected"],
"description": "Production fan-out"
}'
# test ping
curl -X POST https://app.storylayer.ai/api/v1/webhooks/wh_.../test \
-H "Authorization: Bearer sl_pat_..."
# delivery history (filter with ?status=failed,permanent_failure)
curl https://app.storylayer.ai/api/v1/webhooks/wh_.../deliveries \
-H "Authorization: Bearer sl_pat_..."
# alerts feed (in-app health events for your endpoints)
curl 'https://app.storylayer.ai/api/v1/webhooks/alerts?unread_only=true' \
-H "Authorization: Bearer sl_pat_..."
# reactivate after auto-disable
curl -X POST https://app.storylayer.ai/api/v1/webhooks/wh_.../reactivate \
-H "Authorization: Bearer sl_pat_..."Best practices
- Respond with
200as soon as you've persisted the payload — do work asynchronously. - Verify the signature in constant time (
timingSafeEqual), never===. - De-duplicate using the
x-storylayer-event-idheader — retries and replays reuse the same id. - Allow at least one full minute of clock skew when checking
x-storylayer-timestamp(rare, but helpful). - Subscribe to
webhooks/alertsin your monitoring dashboard so a quietly-broken endpoint surfaces fast.