This walks through wiring a rollfuse webhook to a route in your own application: register an endpoint, handle the delivered payload with a minimal Express server, and use the delivery history to figure out why a specific delivery failed.
Register a webhook endpoint
A webhook endpoint is registered under a project with POST /v1/projects/{project_id}/webhook-endpoints. You choose the URL it delivers to and the event types it subscribes to:
const response = await fetch(
`https://api.rollfuse.com/v1/projects/${projectId}/webhook-endpoints`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://example.com/webhooks/rollfuse",
event_types: ["flag.published", "rollout.rolled_back"],
}),
},
);
const endpoint = await response.json();
console.log(endpoint.secret);
The response is the only place secret is ever returned in plaintext. Store it now, because a later GET on the endpoint's metadata never includes it again. A project can hold at most 20 webhook endpoints, and a single endpoint can subscribe to at most 20 event types; going over either limit comes back as a 400.
Handle the delivered payload
Every delivery is a JSON body with an envelope shape: event_type, event_version, occurred_at, and data. Here is a minimal, complete Express handler that reads that payload and reacts to a flag.published event:
import express from "express";
const app = express();
app.use(express.json());
app.post("/webhooks/rollfuse", (req, res) => {
const { event_type, event_version, occurred_at, data } = req.body;
if (event_type === "flag.published") {
console.log(
`[${occurred_at}] flag ${data.feature_flag_id} in environment ${data.environment_id} is now ${data.enabled ? "enabled" : "disabled"}`,
);
// invalidate a local cache, refresh a dashboard, notify a channel, etc.
}
if (event_type === "rollout.rolled_back") {
console.log(`[${occurred_at}] rollout was rolled back:`, data);
}
res.status(200).end();
});
app.listen(3000);
Respond with a 2xx status as soon as you have accepted the payload; do any slow follow-up work (cache invalidation, sending a Slack message, kicking off another job) after you have already replied, not before. rollfuse does not document a signature scheme for verifying that a delivery genuinely came from rollfuse in this endpoint, so treat the payload the way you would any other webhook body from a third party: validate the fields you actually rely on and keep the route unguessable if you need an extra layer of protection.
Debug a failed delivery
If your endpoint was down or returned an error, rollfuse keeps a record of every delivery attempt. List them with GET /v1/webhook-endpoints/{webhook_endpoint_id}/deliveries:
const response = await fetch(
`https://api.rollfuse.com/v1/webhook-endpoints/${endpointId}/deliveries?limit=20`,
{ headers: { Authorization: `Bearer ${token}` } },
);
const deliveries = await response.json();
const failed = deliveries.filter((d) => d.status !== "delivered");
console.log(failed.map((d) => ({ id: d.id, reason: d.last_failure_reason })));
The list view is metadata only, newest first: status, attempt_count, last_attempted_at, next_attempt_at, and last_failure_reason. To see the exact payload that was sent for one delivery, fetch it individually:
const response = await fetch(
`https://api.rollfuse.com/v1/webhook-endpoints/${endpointId}/deliveries/${deliveryId}`,
{ headers: { Authorization: `Bearer ${token}` } },
);
const delivery = await response.json();
console.log(delivery.payload);
That single-delivery response includes the full enveloped payload that was signed and sent, so you can replay it locally against your handler and confirm the fix before waiting for the next real event.
Turning an endpoint off
If you need to stop deliveries without losing the history, disable the endpoint instead of deleting anything:
await fetch(
`https://api.rollfuse.com/v1/webhook-endpoints/${endpointId}/disable`,
{ method: "POST", headers: { Authorization: `Bearer ${token}` } },
);
Disabling is a lifecycle transition, not a delete: the endpoint and every delivery already recorded against it stay intact, and no new delivery is created for it going forward.
With a webhook reacting to flag changes, a natural next step is syncing external metric data into rollfuse the same way: see Sync external metrics into rollfuse with connectors.