Skip to main content

appointment.orphan.detected

A Tour's stored Google Calendar event was not reconcilable with the owning agency's calendar (missing, moved, or malformed).

Category: Appointment Lifecycle

Emitted when: Nightly Celery beat sweep (appointment_orphan_sync) at 02:00 UTC finds a stored calendarEventId that Google returns 404 for or that lives on a different calendar.

Envelope

Every delivery wraps the event-specific data block in the canonical envelope documented on the webhook overview page. The body carries SIX keys; the HMAC signature is delivered as the X-Webhook-Signature request header on the inbound POST to your endpoint (NOT as a body field; including it in the body would make the HMAC a function of its own output).

FieldTypeDescription
event_idstringULID; globally unique. Safe to persist as the dedup key.
event_typestringAlways appointment.orphan.detected for this event.
api_versionstringPayload schema version (YYYY-MM-DD).
timestampintegerUnix epoch seconds at dispatch time.
noncestringULID; unique per delivery, used for replay rejection.
dataobjectEvent-specific payload (see below).

Delivery also sets three HTTP headers on the inbound POST:

HeaderMeaning
X-Webhook-Event-IdMirrors event_id; cheaper than parsing the body to dedup.
X-Webhook-TimestampMirrors timestamp for replay-window checks.
X-Webhook-Signaturesha256=<hex> HMAC over timestamp.body.

Data schema

FieldTypeRequiredDescription
agency_idstringyesOwning media_agency user id
listing_numberstringyesPublic listing number, 7-char lowercase nanoid ([a-z0-9]{7})
tour_idstringyesHex ObjectId of the Tour document
stored_calendar_event_idstringyesThe Google Calendar event id that WAS stored on the Tour but could not be verified.
issue_type"event_missing" | "event_moved" | "event_malformed"yesIssue Type
stored_scheduled_atstringyesUTC moment the Tour's cached scheduledDate said the appointment was FOR (what the database believed).
detected_atstringyesUTC moment the sweep detected the orphan.
sweep_run_idstringyesStable identifier shared across all events emitted during the same sweep run.
detected_bystringyesActor token; always 'system:appointment_orphan_sync' for this event.

Sample payload

The sample below is a live fixture. Feed the exact body bytes shown plus the timestamp into the HMAC-SHA256 recipe from the webhook overview using the documented test-vector secret (test_secret_001 ) and you will recover the signature header value printed below.

Body (delivered as the HTTP request body):

{
"event_id": "evt_4JZDSWT6HHQ17HX485GM2BCMXM",
"event_type": "appointment.orphan.detected",
"api_version": "2026-04-17",
"timestamp": 1745339401,
"nonce": "4MF6VDF5QRE3P28Y1GWBQG7S6A",
"data": {
"agency_id": "user_01HXAGENCY0000000000000",
"listing_number": "abc1234",
"tour_id": "66312a4b5c6d7e8f90a1b2c3",
"stored_calendar_event_id": "evt_cal_01HXCALEVT0000000000",
"issue_type": "event_missing",
"stored_scheduled_at": "2026-05-29T12:00:00Z",
"detected_at": "2026-05-29T12:00:00Z",
"sweep_run_id": "01HXSWEEPRUN00000000000000",
"detected_by": "system:reconciliation_sweep"
}
}

Signature header (delivered as X-Webhook-Signature on the same request):

sha256=8dc8a6905300def4b96e03f145d64da04822520918a96aedcf45de0fbb510698

Retry behaviour

Failed deliveries retry on the sequence 2, 4, 8, 16, 32 seconds (five attempts plus the initial call for a total of six over approximately 62 seconds of wall clock). After the final attempt the delivery lands in the dead-letter queue and the dashboard operator can replay it from Settings > API Keys > Webhook Endpoint > Deliveries. See the webhook overview for the full rules.

Verification

Every receiver MUST verify the X-Webhook-Signature header using the recipe in the webhook overview. Rejecting deliveries whose timestamp is more than 5 minutes off your wall clock (after NTP correction on your side) or whose nonce has already been consumed in the last 10 minutes is part of the contract.