Appearance
RowanX Transportation Booking Integration
Activation required
The URLs and schemas below are the agreed integration contract. Do not send traffic until Sanna confirms that the environment is active and provides its credentials.
RowanX is a booking supplier for Sanna Transfer. RowanX sends transportation bookings to Sanna, and Sanna operates those bookings afterward.
Download the complete OpenAPI 3.1 schema
What RowanX must implement
- Send a signed
booking.created,booking.updated, orbooking.cancelledevent to the correct Sanna webhook URL. - Use the booking
idas the stable RowanX reference in every later event. - Retry temporary failures without creating a different event.
- Provide one staging and one production callback URL where Sanna can send final booking statuses.
Environments
Each environment has its own URL, key ID, and secret. Never use staging credentials in production.
| Environment | RowanX sends bookings to |
|---|---|
| Staging | https://sanna-transfer.ha0-nguyen.workers.dev/webhook/partner/4 |
| Production | https://sanna-transfer.thecodeorigin.workers.dev/webhook/partner/4 |
Both endpoints accept:
http
POST /webhook/partner/4
Content-Type: application/jsonIntegration flow
- RowanX → Sanna: create, update, or cancel a booking.
- Sanna → RowanX: report the final
COMPLETEDorNO_SHOWstatus.
Events sent by RowanX
| Event | When to send it | Body |
|---|---|---|
booking.created | A customer confirms a new transfer | Complete booking snapshot |
booking.updated | Accepted booking details change | Complete latest booking snapshot |
booking.cancelled | The booking is cancelled before operation | Booking ID and cancellation reason |
Send events for one booking in order. bookingVersion starts at 1 and increases by exactly 1 whenever RowanX changes that booking.
Accepted booking schema
The booking object for create and update contains:
| Field | Type | Required | Meaning |
|---|---|---|---|
id | string | Yes | Stable RowanX booking reference |
departureTime | RFC 3339 string | Yes | Pickup time with Z or an explicit UTC offset |
departure.name | string | Yes | Pickup location name or address |
departure.latitude | number | Yes | WGS84 latitude, -90 to 90 |
departure.longitude | number | Yes | WGS84 longitude, -180 to 180 |
arrival.name | string | Yes | Drop-off location name or address |
arrival.latitude | number | Yes | WGS84 latitude, -90 to 90 |
arrival.longitude | number | Yes | WGS84 longitude, -180 to 180 |
passenger.name | string | Yes | Lead passenger name |
passenger.phone | string | Yes | International format recommended |
passenger.count | integer | Yes | Total number of passengers |
vehicleCategory | string | No | RowanX vehicle category |
flightNumber | string | No | Flight number when applicable |
notes | string | No | Operational notes only; no secrets or payment data |
Create booking example
json
{
"eventId": "evt_01J5V1Y8F8K3R4Z7Q2M6A9C0DE",
"eventType": "booking.created",
"occurredAt": "2026-08-23T08:30:00Z",
"bookingVersion": 1,
"booking": {
"id": "ROWANX-48291",
"departureTime": "2026-09-05T08:30:00+07:00",
"departure": {
"name": "Da Nang International Airport",
"latitude": 16.0439,
"longitude": 108.1994
},
"arrival": {
"name": "Hoi An Ancient Town",
"latitude": 15.8801,
"longitude": 108.3380
},
"passenger": {
"name": "Nguyen Van A",
"phone": "+84901234567",
"count": 3
},
"vehicleCategory": "sedan",
"flightNumber": "VN123"
}
}Update booking example
An update sends the complete current booking again. The id remains unchanged and the version increases.
json
{
"eventId": "evt_01J5V2C6YX7D4Z1Q9B3N8F0KLM",
"eventType": "booking.updated",
"occurredAt": "2026-08-23T09:15:00Z",
"bookingVersion": 2,
"booking": {
"id": "ROWANX-48291",
"departureTime": "2026-09-05T09:00:00+07:00",
"departure": {
"name": "Da Nang International Airport",
"latitude": 16.0439,
"longitude": 108.1994
},
"arrival": {
"name": "Hoi An Ancient Town",
"latitude": 15.8801,
"longitude": 108.3380
},
"passenger": {
"name": "Nguyen Van A",
"phone": "+84901234567",
"count": 3
},
"vehicleCategory": "sedan",
"flightNumber": "VN123"
}
}Cancel booking example
json
{
"eventId": "evt_01J5V2Q7BD9S8E1TC4K6G0M3NP",
"eventType": "booking.cancelled",
"occurredAt": "2026-08-23T10:00:00Z",
"bookingVersion": 3,
"bookingId": "ROWANX-48291",
"reason": "Customer cancelled the booking"
}Status sent by Sanna to RowanX
RowanX provides callback URLs during onboarding. Sanna calls the matching environment after operations marks the booking complete or no-show.
json
{
"eventId": "status_01J6A4YH5D9BX2K8P7C3N0Q1RM",
"eventType": "booking.status.updated",
"occurredAt": "2026-09-05T03:15:00Z",
"bookingId": "ROWANX-48291",
"status": "COMPLETED"
}Allowed final statuses:
| Status | Meaning |
|---|---|
COMPLETED | Passenger transfer completed |
NO_SHOW | Passenger did not arrive for pickup |
RowanX should acknowledge a valid status callback with any 2xx response. Sanna retries temporary failures. Both teams will agree the callback URLs and callback credential before staging acceptance.
Authentication
Sanna provides a different key ID and secret for staging and production. RowanX signs the exact JSON bytes and sends:
http
X-Sanna-Key-Id: <environment key ID>
X-Sanna-Webhook-Timestamp: <current Unix seconds>
X-Sanna-Webhook-Signature: v1=<base64 HMAC-SHA256>Build the signature from:
text
keyId + "." + timestamp + "." + exactRawRequestBodySerialize JSON once, sign those exact bytes, and send those same bytes. Sanna rejects timestamps more than 300 seconds from its clock. Never place secrets in source control, URLs, tickets, screenshots, or logs.
Sanna response
An accepted event returns:
json
{
"status": "accepted",
"eventId": "evt_01J5V1Y8F8K3R4Z7Q2M6A9C0DE",
"bookingId": "ROWANX-48291"
}An identical retry returns 200 with status: "duplicate" and does not create another booking.
| HTTP | RowanX action |
|---|---|
200 | Accepted or identical duplicate; stop retrying |
400 or 422 | Correct the request before sending a new event |
401 | Correct credentials, timestamp, or signature |
409 | Correct booking version or lifecycle conflict |
429 | Wait for Retry-After, then retry |
500 or 503 | Retry with exponential backoff and jitter |
For a retry, keep the same body, eventId, and bookingVersion; generate a fresh timestamp and signature.
Staging acceptance checklist
- Create one booking and verify it appears once in the Sanna dashboard.
- Retry the identical create event and verify no duplicate booking is created.
- Update its departure time and coordinates with version
2. - Cancel a pre-pickup test booking.
- Confirm invalid signatures produce no booking change.
- Confirm Sanna can send
COMPLETEDandNO_SHOWto the RowanX staging callback.
Production credentials are issued only after both teams complete these staging checks.