Webhooks
Subscribe to Rapidfolio run events and receive signed HTTP notifications to your server.
Overview
Webhooks let Rapidfolio notify your systems the moment something happens — a run completes, a run fails, a human review is needed, or a procedure emits a custom event. Rapidfolio sends a signed POST request to your configured URL with the event payload.
Webhooks are the preferred alternative to polling for most production integrations.
Supported Events
| Event | Description |
|---|---|
run_completed | A procedure run finished successfully |
run_failed | A procedure run finished with an error |
human_review_requested | A run paused at a Human Review node and is awaiting a decision |
custom.<name> | A custom event emitted by a procedure step (e.g. custom.payment_flagged) |
Setup
Via the Dashboard
- Go to Settings → Webhooks in the Rapidfolio dashboard.
- Click New Webhook.
- Enter your endpoint URL and select the events you want to receive.
- Copy the generated webhook secret — you will use it to verify signatures.
Via the API
POST https://app.rapid.io/api/v1/webhooks
Authorization: Bearer <api_key>
Content-Type: application/json
{
"name": "My server",
"url": "https://api.example.com/rapid-webhook",
"events": ["run_completed", "run_failed", "human_review_requested"],
"isActive": true
}
Response
{
"id": "wh_abc123",
"name": "My server",
"url": "https://api.example.com/rapid-webhook",
"events": ["run_completed", "run_failed", "human_review_requested"],
"isActive": true,
"secret": "whsec_xxxxxxxxxxxx",
"createdAt": "2026-02-27T10:00:00.000Z"
}
The secret is returned once at creation. Store it securely — you will need it to verify incoming webhook signatures.
Payload Structure
Every webhook delivery has the same envelope:
{
"id": "delivery_abc",
"event": "run_completed",
"createdAt": "2026-02-27T10:00:05.000Z",
"data": {
"runId": "run_clxyz123",
"procedureId": "proc_abc",
"environment": "sandbox",
"status": "completed",
"output": { "approved": true }
}
}
Envelope Fields
| Field | Description |
|---|---|
id | Unique delivery ID — useful for deduplication |
event | The event name that triggered this delivery |
createdAt | ISO 8601 timestamp of when the event occurred |
data | Event-specific payload — see per-event payloads below |
run_completed data
| Field | Description |
|---|---|
runId | ID of the completed run |
procedureId | ID of the procedure |
environment | sandbox or live |
status | Always completed |
output | The procedure's final output object |
run_failed data
| Field | Description |
|---|---|
runId | ID of the failed run |
procedureId | ID of the procedure |
environment | sandbox or live |
status | Always failed |
error | Error message describing the failure |
human_review_requested data
| Field | Description |
|---|---|
runId | ID of the paused run |
procedureId | ID of the procedure |
environment | sandbox or live |
reviewTitle | Human-readable title configured on the review node |
reviewData | Data object the reviewer should inspect |
Signature Verification
Every delivery includes an X-Rapidfolio-Signature header:
X-Rapidfolio-Signature: sha256=<hmac_hex>
The HMAC is computed over the raw request body using HMAC-SHA256 with your webhook's secret. Always verify this signature before processing the payload — it ensures the delivery genuinely came from Rapidfolio and was not tampered with.
import { createHmac, timingSafeEqual } from 'crypto'
function verifyWebhook(body: string, signature: string, secret: string): boolean {
const expected = createHmac('sha256', secret)
.update(body)
.digest('hex')
const actual = signature.replace('sha256=', '')
// Use timing-safe comparison to prevent timing attacks
return timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(actual, 'hex')
)
}
Use the raw request body string — not a parsed JSON object — when computing the HMAC. Parsing and re-serialising may alter whitespace or key order and will cause the signature check to fail.
Express example
import express from 'express'
import { createHmac, timingSafeEqual } from 'crypto'
const app = express()
app.post(
'/rapid-webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-rapidfolio-signature'] as string
const secret = process.env.RAPID_WEBHOOK_SECRET
if (!secret) {
res.sendStatus(500)
return
}
if (!verifyWebhook(req.body.toString(), signature, secret)) {
res.sendStatus(401)
return
}
const payload = JSON.parse(req.body.toString())
// Handle events
switch (payload.event) {
case 'run_completed':
console.log('Run completed:', payload.data.runId)
break
case 'run_failed':
console.error('Run failed:', payload.data.error)
break
case 'human_review_requested':
console.log('Review needed:', payload.data.reviewTitle)
break
}
res.sendStatus(200)
}
)
Important: Your webhook endpoint must respond with a
2xxstatus code to acknowledge receipt. Respond quickly — do heavy processing in the background after acknowledging. Rapidfolio considers any non-2xx response a delivery failure.
Delivery Retries
If your endpoint returns a non-2xx response or does not respond within the timeout, Rapidfolio retries the delivery with exponential backoff. Deliveries that continue to fail after all retry attempts are marked as failed in the delivery history.
Implement idempotent webhook handlers — deliveries may be retried and could arrive more than once. Use the id field (delivery ID) to detect and discard duplicates.
Viewing Delivery History
GET https://app.rapid.io/api/v1/webhooks/:webhookId/deliveries
Authorization: Bearer <api_key>
Returns a paginated list of recent delivery attempts, including their status, HTTP response code, and response body. Use this to debug failed deliveries.
Response
{
"data": [
{
"id": "delivery_abc",
"event": "run_completed",
"url": "https://api.example.com/rapid-webhook",
"status": "delivered",
"responseCode": 200,
"attemptCount": 1,
"createdAt": "2026-02-27T10:00:05.000Z",
"deliveredAt": "2026-02-27T10:00:05.200Z"
}
]
}