Webhooks
Webhook endpoints receive a JSON notification each time a subscribed event
happens in your organization, such as a case update, a finished document
job, or a completed export. Instead of polling, your server listens for
these deliveries. Paths are relative to /api/v1.
Endpoints
GET/webhook-endpoints
Scope: webhooks:read · Paginated
List the webhook endpoints in your organization.
POST/webhook-endpoints
Scope: webhooks:write · Idempotency-Key required
Create an endpoint. Returns 201 Created.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Label for the endpoint, up to 255 characters. |
url | string | Yes | Public https:// URL that receives deliveries. Addresses on private networks are rejected. |
subscriptions | array | Yes | Event types or patterns to receive. See event types. |
The response includes secret, shown only in this
response, along with secret_prefix for identifying
it later. Store the secret when you receive it.
GET/webhook-endpoints/{endpoint_id}
Scope: webhooks:read
Read one endpoint, including its subscriptions,
status, health_score, and secret_prefix.
The secret is never returned here.
PATCH/webhook-endpoints/{endpoint_id}
Scope: webhooks:write · Idempotency-Key required
Update name, url, subscriptions, or status. The URL is validated again.
DELETE/webhook-endpoints/{endpoint_id}
Scope: webhooks:write
Delete an endpoint. Returns 204 No Content.
POST/webhook-endpoints/{endpoint_id}/secret/rotate
Scope: webhooks:write · Idempotency-Key required
Replace the endpoint's secret. The new secret is returned
once in the response. Update your server before the old value is
discarded.
Event types
Subscribe to exact event types or use a pattern:
*receives every event.case.*receives every event whose type starts withcase.(the same works fordocument.*,export.*,user.*, androle.*).
| Event type | Sent when |
|---|---|
case.created | A case is created |
case.updated | A case's details change |
case.workflow_changed | A case moves to another workflow status |
document.uploaded | A document upload completes |
document.processing_started | A processing job starts on a document |
document.processing_completed | A processing job finishes successfully |
document.processing_failed | A processing job fails |
redaction.review_completed | A document's redaction review is completed |
export.completed | An export finishes and can be downloaded |
export.failed | An export fails |
user.invited | A user is invited |
user.updated | A user's details change |
user.deactivated | A user is deactivated |
user.roles_updated | A user's roles change |
role.created | A role is created |
role.updated | A role is updated |
role.deleted | A role is deleted |
Delivery format
Each delivery is an HTTPS POST with a
CloudEvents 1.0 JSON body
(Content-Type: application/cloudevents+json). Payloads are
minimal: they identify the resource and never include file contents, OCR
text, or secrets. Fetch the full resource through the API when you need
more detail.
{
"specversion": "1.0",
"id": "evt_01J...",
"source": "https://redactstripe/organizations/{organization_id}",
"type": "export.completed",
"subject": "exports/exp_...",
"time": "2026-10-13T10:15:00.000Z",
"datacontenttype": "application/json",
"data": {
"object": { "id": "exp_...", "object": "export" },
"links": { "resource": "/v1/exports/exp_..." }
}
}
Headers
| Header | Value |
|---|---|
X-RedactStripe-Event-Id | The event ID, the same as id in the body. Use it to de-duplicate events. |
X-RedactStripe-Delivery-Id | The ID of this delivery attempt. |
X-RedactStripe-Event | The event type. |
X-RedactStripe-Timestamp | When the delivery was signed, in ISO 8601. |
X-RedactStripe-Signature | v1= followed by a hex HMAC-SHA-256. See the notice above. |
User-Agent | RedactStripe-Webhooks/1.0 |
Verifying a signature (planned scheme)
When signatures are available, the signed content is the Unix time in
seconds from the timestamp, a period, and the raw request body. The
expected value is v1= plus the hex HMAC-SHA-256 of that
content using the endpoint secret. Reject deliveries whose timestamp is
more than five minutes old or in the future, and compare signatures in
constant time.
import crypto from "node:crypto";
export function verifyDelivery(rawBody, headers, secret) {
const ts = Math.floor(Date.parse(headers["x-redactstripe-timestamp"]) / 1000);
if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > 300) return false;
const expected = "v1=" + crypto
.createHmac("sha256", secret)
.update(`${ts}.${rawBody}`, "utf8")
.digest("hex");
const received = headers["x-redactstripe-signature"] || "";
return received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
}
Responses and retries
- Respond with any
2xxstatus within 10 seconds to acknowledge a delivery. Anything slower counts as a timeout. - Timeouts, network errors,
408,429, and5xxresponses are treated as retryable. Other4xxresponses are not retried. - Each endpoint has a
health_scorethat drops when deliveries fail. - Deliveries can be read and replayed manually, described below.
Deliveries
GET/webhook-endpoints/{endpoint_id}/deliveries
Scope: webhooks:read · Paginated
List the delivery attempts for an endpoint, including their status and any response code.
GET/webhook-deliveries/{delivery_id}
Scope: webhooks:read
Read one delivery.
POST/webhook-deliveries/{delivery_id}/replay
Scope: webhooks:write · Idempotency-Key required
Queue the same event for delivery again, for example after your server was down or after you fixed a bug in your handler. The replay has its own delivery ID and is returned with its status.