RapidfolioRapidfolio
API Reference

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

EventDescription
run_completedA procedure run finished successfully
run_failedA procedure run finished with an error
human_review_requestedA 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

  1. Go to Settings → Webhooks in the Rapidfolio dashboard.
  2. Click New Webhook.
  3. Enter your endpoint URL and select the events you want to receive.
  4. 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

FieldDescription
idUnique delivery ID — useful for deduplication
eventThe event name that triggered this delivery
createdAtISO 8601 timestamp of when the event occurred
dataEvent-specific payload — see per-event payloads below

run_completed data

FieldDescription
runIdID of the completed run
procedureIdID of the procedure
environmentsandbox or live
statusAlways completed
outputThe procedure's final output object

run_failed data

FieldDescription
runIdID of the failed run
procedureIdID of the procedure
environmentsandbox or live
statusAlways failed
errorError message describing the failure

human_review_requested data

FieldDescription
runIdID of the paused run
procedureIdID of the procedure
environmentsandbox or live
reviewTitleHuman-readable title configured on the review node
reviewDataData 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 2xx status 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"
    }
  ]
}

On this page