Skip to content

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

  1. Send a signed booking.created, booking.updated, or booking.cancelled event to the correct Sanna webhook URL.
  2. Use the booking id as the stable RowanX reference in every later event.
  3. Retry temporary failures without creating a different event.
  4. 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.

EnvironmentRowanX sends bookings to
Staginghttps://sanna-transfer.ha0-nguyen.workers.dev/webhook/partner/4
Productionhttps://sanna-transfer.thecodeorigin.workers.dev/webhook/partner/4

Both endpoints accept:

http
POST /webhook/partner/4
Content-Type: application/json

Integration flow

RowanX and Sanna Transfer booking flow

  • RowanX → Sanna: create, update, or cancel a booking.
  • Sanna → RowanX: report the final COMPLETED or NO_SHOW status.

Events sent by RowanX

EventWhen to send itBody
booking.createdA customer confirms a new transferComplete booking snapshot
booking.updatedAccepted booking details changeComplete latest booking snapshot
booking.cancelledThe booking is cancelled before operationBooking 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:

FieldTypeRequiredMeaning
idstringYesStable RowanX booking reference
departureTimeRFC 3339 stringYesPickup time with Z or an explicit UTC offset
departure.namestringYesPickup location name or address
departure.latitudenumberYesWGS84 latitude, -90 to 90
departure.longitudenumberYesWGS84 longitude, -180 to 180
arrival.namestringYesDrop-off location name or address
arrival.latitudenumberYesWGS84 latitude, -90 to 90
arrival.longitudenumberYesWGS84 longitude, -180 to 180
passenger.namestringYesLead passenger name
passenger.phonestringYesInternational format recommended
passenger.countintegerYesTotal number of passengers
vehicleCategorystringNoRowanX vehicle category
flightNumberstringNoFlight number when applicable
notesstringNoOperational 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:

StatusMeaning
COMPLETEDPassenger transfer completed
NO_SHOWPassenger 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 + "." + exactRawRequestBody

Serialize 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.

HTTPRowanX action
200Accepted or identical duplicate; stop retrying
400 or 422Correct the request before sending a new event
401Correct credentials, timestamp, or signature
409Correct booking version or lifecycle conflict
429Wait for Retry-After, then retry
500 or 503Retry 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 COMPLETED and NO_SHOW to the RowanX staging callback.

Production credentials are issued only after both teams complete these staging checks.

Last updated:

Sanna Transfer × RowanX API Booking Event API — Draft proposal