Developer
WebhookWebhook events
The booking.completed event, the JSON envelope and payload your endpoint receives, headers, and delivery and retry behavior.
Updated Sep 30, 2026
ServiceReserve sends a webhook event when a customer completes a
booking. You configure endpoints on the Webhooks page. They apply
to every booking form in the workspace. For the default webhook
endpoint kind, each event arrives as a JSON POST. The other kinds are
covered in Endpoint kinds.
Event types
ServiceReserve emits a single event today. Any events added later will use the same envelope.
booking.completedEnvelope
Every payload has this top-level shape.
eventrequiredorganization_idform_idsent_atrequireddatarequiredbooking.completed payload
{
"event": "booking.completed",
"organization_id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
"sent_at": "2026-09-29T18:33:43.120Z",
"data": {
"request_id": "f0e9c1d8-94c3-4d50-8f81-2b63f4a47a4c",
"form_id": "bd28906b-7f9a-4aaf-ab86-85c1a1ccd4f2",
"contact": {
"first_name": "Jane",
"last_name": "Doe",
"email": "[email protected]",
"phone": "+15555550123",
"company": null
},
"address": {
"address": "123 Main St",
"address2": "Unit 4",
"city": "Austin",
"state": "TX",
"zip": "78701",
"country": "US",
"entrance_info": "Gate code 1234"
},
"category": {
"id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"name": "Drains",
"intake": [
{ "question": "Which drain is clogged?", "answer": "Kitchen sink", "type": "select" }
],
"services": [
{
"id": "6d1f0c2a-3b4e-4f5a-9c8d-7e6f5a4b3c2d",
"name": "Drain cleaning",
"price": 195,
"priceFrom": null,
"priceDisplay": true,
"free": false,
"durationMinutes": 60,
"intake": []
}
]
},
"services": [
{
"id": "6d1f0c2a-3b4e-4f5a-9c8d-7e6f5a4b3c2d",
"name": "Drain cleaning",
"price": 195,
"priceFrom": null,
"priceDisplay": true,
"free": false,
"durationMinutes": 60,
"intake": []
}
],
"total_amount": 195,
"intake_responses": [
{
"question": "How old is the system?",
"answer": "5-10 years",
"type": "select",
"scope": "template",
"scope_name": "Plumbing services"
},
{
"question": "Which drain is clogged?",
"answer": "Kitchen sink",
"type": "select",
"scope": "category",
"scope_name": "Drains"
}
],
"template_intake": [
{ "question": "How old is the system?", "answer": "5-10 years", "type": "select" }
],
"preferred_time": { "date": "2026-10-06", "slot": "morning" },
"attribution": {
"source": null,
"page_url": "https://example.com/book?utm_source=google&utm_medium=cpc&utm_campaign=fall-sale",
"referrer": "https://www.google.com/",
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "fall-sale"
},
"profile": {
"id": "52c58cc6-c7fb-4419-ad88-9c4a0e89ad2f",
"address": "434 Northwest 1st Avenue, Fort Lauderdale, FL 33301",
"city": "Fort Lauderdale",
"state": "FL",
"tag": "FL-South"
},
"source": null
}
}Payload fields
request_idform_idcontactaddresscategoryservicestotal_amountintake_responsestemplate_intakepreferred_timeattributionprofilesourceThe profile block carries the business profile the booking was
attributed to. It includes the profile's street, city, and state plus
the tag you set under Profile tag on the profile's page. Use the
tag to route or group bookings (per-territory queues, per-campaign
Slack channels) without parsing UTM campaigns. The tag is copied when
the booking is submitted, so editing it later doesn't change past
events.
Headers
A webhook-kind endpoint receives these headers:
Content-TypeUser-AgentX-ServiceReserve-EventX-ServiceReserve-Signatureapi-kind endpoints also send User-Agent and X-ServiceReserve-Event
unless your headers template overrides them. telegram-kind endpoints
send none of these headers.
Delivery semantics
- Queued, not inline. A booking is saved first. Deliveries then go through a background queue, normally within a second or two.
- Timeout. 10 seconds. An endpoint that doesn't respond in time counts as a failed attempt.
- Retries. A failed attempt (non-2xx response, network error, or timeout) is retried up to 2 more times. After that the delivery is marked failed.
- Duplicates are possible. For example, your endpoint might process
a request but time out before it responds. Make your handler
idempotent on
data.request_id.
Delivery history and re-sending
The Recent deliveries panel on the Webhooks page lists the last 50 deliveries, newest first, with status code, duration, and any error.
To send an event again for a specific booking, open the request. In
the Integration activity card, pick Webhooks from the
integration list and click Send. Resends go to the endpoints on the
Webhooks page. The event is rebuilt
from the stored request, so it differs from the original event.
category and total_amount are null. services holds the stored
service ids rather than service objects. intake_responses holds the
stored intake objects (the same shape as the Get request API).
profile and template_intake are omitted, and created_at is added.
Test deliveries
The Send test button on each endpoint sends a synthetic
booking.completed event with data.test: true. The test body is a
short fixture (test, a sample booking object, and a message), not
a full booking payload. Filter on data.test in your sink so test
sends don't pollute production data.
Enabling and disabling
The toggle in the Webhooks page header turns delivery on or off for every endpoint at once. It is the same switch as the Webhooks card on the Integrations page.
Related
Verify ServiceReserve webhook deliveries with the HMAC-SHA256 signature computed from your endpoint's signing secret.
How ServiceReserve delivers events to each endpoint kind — signed webhook, custom HTTP request, or legacy Telegram message.
Authenticate ServiceReserve API requests with a Bearer key issued from your workspace, and handle rate limits and errors.