Webhooks API
Webhooks send HTTPS POST requests to your endpoint when events happen in your Cloud. Each request contains a JSON payload for one event, such as a new customer, an approved booking, or a captured payment. Delivery is asynchronous and usually begins within seconds.
Use REV23 on Zapier if you do not need to host your own endpoint. Webhooks & Integrations compares the options.
Available events
Section titled “Available events”Events are grouped by category. Sales, Payroll, and Booking events require the corresponding feature. REV23 omits events for disabled features from the subscription picker.
Customers
Section titled “Customers”| Event | Fires when |
|---|---|
customer.created | A new customer is created |
customer.updated | A customer is updated |
idea.created | A customer submits an idea |
idea.declined | An idea is declined |
Services
Section titled “Services”| Event | Fires when |
|---|---|
service.created | A new service is created |
service.completed | A service is marked complete |
service.user_assigned | An artist is assigned to a service |
service_session.created | A new session is created |
service_session.started | A session is started |
service_session.ended | A session ends |
| Event | Fires when |
|---|---|
release_form.signed | A customer signs a release form |
release_form.generated | A release form PDF is generated |
care_journey.started | A care journey begins |
care_journey.review_submitted | A customer submits a care review |
Sales (requires the Sales feature)
Section titled “Sales (requires the Sales feature)”| Event | Fires when |
|---|---|
checkout.completed | A checkout is completed |
checkout.voided | A checkout is voided |
checkout.refunded | A checkout is refunded |
payment.captured | A payment is captured |
payment.voided | A payment is voided |
payment.refunded | A payment is refunded |
credit.created | Store or deposit credit is issued |
credit.redeemed | Credit is applied to a sale |
gift_card.created | A gift card is issued |
gift_card.redeemed | A gift card is redeemed |
Payroll (requires the Payroll feature)
Section titled “Payroll (requires the Payroll feature)”| Event | Fires when |
|---|---|
pay_period.closed | A pay period is closed |
payout.processed | A payout is recorded |
earning.created | An earning is recorded |
earning.reversed | An earning is reversed |
Booking (requires the Booking feature)
Section titled “Booking (requires the Booking feature)”| Event | Fires when |
|---|---|
booking.created | A booking is created |
booking.updated | A booking is updated |
booking.rescheduled | A booking is moved to a new time |
booking.rebooked | A cancelled, no-show, or lapsed booking is rebooked |
booking.pending_approval | A booking request is waiting on approval |
booking.approved | A booking is approved |
booking.cancelled | A booking is cancelled |
booking.declined | A booking request is declined |
booking.noshow | A booking is marked a no-show |
booking.converted | A booking is converted into a service |
| Event | Fires when |
|---|---|
user.created | A new user is created |
The same catalog is available at GET /api/v2/webhooks/events. Each result contains the event slug, display name, category, and required feature.
Event scoping
Section titled “Event scoping”Each subscription belongs to the user who created it. Most events apply to the entire Cloud, so every active subscription for that event receives them.
These events are scoped to the assigned user:
| Event | Assigned user |
|---|---|
idea.created | Artist assigned to the idea |
service.*, service_session.*, release_form.* | Artist assigned to the service |
care_journey.started | Artist assigned to the service |
payout.processed, earning.* | Payout or earning recipient |
A subscription receives a user-scoped event when its creator is the assigned user, the Cloud owner, or a user with Webhooks → Subscribe (All). If the source record has no assigned user, every active subscription for that event receives it.
All other events in the catalog are Cloud-wide.
Create and manage a subscription
Section titled “Create and manage a subscription”Go to Settings → Integrations → Webhooks. A subscription contains:
- Events — One or more events sent to the same endpoint.
- URL — The HTTPS endpoint that receives the POST requests.
- Active — Whether REV23 sends events for this subscription.
- Custom Headers — Optional headers included with every attempt, such as an authorization header for your endpoint.
REV23 generates a signing secret when you create the subscription. You can reveal or copy it from the subscription later. Use it to verify each request.
You can edit, deactivate, or delete a subscription from the same page. Recent Deliveries lists the latest 50 attempts across your subscriptions, including the request body, result, HTTP status code, and response body.
Payload format
Section titled “Payload format”Every delivery uses the same JSON envelope. The event-specific payload lives in data.
This is an abridged customer.created delivery from Midnight Matinee Tattoo. The full data object contains more customer fields.
{ "type": "customer.created", "id": "whe_9m4k2p7x6q1a", "version": 2, "createdOnUtc": "2026-08-25T10:30:00+00:00", "tenantName": "midnightmatinee", "data": { "id": "cus_4f8m2k9p7x6q", "givenName": "Verity", "familyName": "Blackwood", "fullName": "Verity Blackwood", "email": "verity.blackwood@example.com", "createdOnUtc": "2026-08-25T10:29:58+00:00" }}| Field | Meaning |
|---|---|
type | Event slug. Use it to route events handled by the same endpoint. |
id | Webhook event ID. It remains the same across retries. |
version | Envelope schema version, currently 2. |
createdOnUtc | Server time when the event fired. |
tenantName | Cloud identifier. Use it when one endpoint serves multiple Clouds. |
data | Event-specific payload in camel case. Its fields depend on type. |
Signature verification
Section titled “Signature verification”Every webhook request includes an x-rev23-signature header. REV23 computes its HMAC-SHA256 digest from the raw request body and the subscription secret.
The header format is:
x-rev23-signature: sha256=<hex-digest>Verify the signature before processing the event. Use the raw request body exactly as received; parsing and re-serializing the JSON changes the digest.
const crypto = require('crypto');
function isSignatureValid(rawBody, secret, signatureHeader) { const hmac = crypto.createHmac('sha256', secret); hmac.update(rawBody); const expected = Buffer.from(`sha256=${hmac.digest('hex')}`); const received = Buffer.from(signatureHeader ?? ''); return ( received.length === expected.length && crypto.timingSafeEqual(received, expected) );}import hmacimport hashlib
def is_signature_valid( raw_body: bytes, secret: str, signature_header: str | None) -> bool: expected = "sha256=" + hmac.new( secret.encode("utf-8"), raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature_header or "", expected)Delivery behavior
Section titled “Delivery behavior”Webhook delivery is at least once. Your endpoint may receive the same event more than once, so store the envelope id and ignore repeats before performing work that cannot safely run twice.
| Rule | Behavior |
|---|---|
| Acknowledgment | Any 2xx response succeeds. Other responses count as failures. |
| Timeout | REV23 waits 30 seconds for a response. |
| Retries | REV23 makes up to 10 attempts for a failed event. Every attempt uses the same envelope id. |
| HTTP 410 | 410 Gone deactivates the subscription immediately and is not retried. |
| Repeated failures | 15 consecutive failed attempts deactivate the subscription. |
| Custom headers | REV23 includes the configured headers with every attempt. |
Permissions
Section titled “Permissions”Creating and managing subscriptions requires Webhooks → Subscribe. No built-in role includes it; the owner grants it deliberately. With Webhooks → Subscribe (All), that user’s subscriptions also receive events assigned to other users. See Roles & Permissions.