Supplier Integration Guide
Phase 1: Quote Requests, Response Handling & Webhook Notifications
Introduction
This integration guide defines the protocol for transportation suppliers to integrate with Moro's platform during Phase 1. It covers how to handle quote requests, respond with availability and pricing, and notify Moro of trip events through webhooks.
By implementing this integration, suppliers can offer their vehicles and services through Moro's marketplace, enabling seamless booking experiences for passengers while maintaining control over their supply operations.
User Journey: How Moro Aggregates Your Services
Moro is a mobility aggregator that connects passengers with multiple transportation suppliers. Understanding the complete user journey helps you implement the integration correctly and see where your service fits in the ecosystem.
Journey Steps Explained
Step 1: User Requests a Ride
The user opens the Moro aggregator app and enters their pickup location, dropoff location, and desired time (or selects "ASAP" for immediate pickup).
Data sent: pickup coordinates, dropoff coordinates, dateScheduled (null for ASAP), referenceId
Step 2: Moro Sends Quote Requests in Parallel
Moro simultaneously sends quote requests to all registered suppliers (including your service). Each supplier receives the same trip details.
Your endpoint: POST https://your-api.com/quote
Step 3: Suppliers Respond with Quotes
Your system calculates pricing, checks vehicle availability, and returns one or more quotes. Moro displays all quotes to the user in real-time as they arrive.
Response: Array of quotes with pricing, vehicle type, ETA, and nearest fleet information
Step 4: User Selects Your Supplier
After comparing quotes, the user selects your service. Moro redirects them to your mobile app using a deep link with all necessary booking parameters.
yourapp://book?quoteId={quoteId}&referenceId={refId}&pickupLat={lat}&pickupLng={lng}&dropoffLat={lat}&dropoffLng={lng}&promoCode={code}&affiliateCode={code}&passengerName={name}&passengerPhone={phone}
Deep Link Parameters:
quoteId- Your quote identifier returned in step 3referenceId- Moro's session reference (UUID) - CRITICAL for webhookspickupLat&pickupLng- Pickup coordinatesdropoffLat&dropoffLng- Dropoff coordinatespromoCode- Promotional discount code (if applicable)affiliateCode- Affiliate/partner tracking codepassengerName- Passenger's name for driver referencepassengerPhone- Passenger's phone numberscheduledTime- Requested pickup time (ISO 8601 format, or null for ASAP)paymentMethod- Preferred payment method (cash, card, wallet, etc.)specialRequests- Any special requirements (wheelchair accessible, child seat, etc.)
Step 5: User Confirms Trip in Your App
Your mobile app opens with the trip details pre-filled. The user reviews the information and confirms the booking. Your system assigns a driver and creates the trip.
Action: Create trip in your system, assign driver, prepare to send webhook
Step 6: Send trip.booked Webhook to Moro
Immediately after the trip is confirmed and driver is assigned, your system must send a webhook notification to Moro with the trip.booked event.
POST https://api.moro.sa/v1/webhooks/supplier-events
Required Headers: X-API-Key, X-Signature (HMAC-SHA256), Content-Type: application/json
Payload: Must include the original referenceId from the quote request
Step 7: Fleet Tracking While User Decides
After suppliers return quotes and while the user session is still active (between receiving quotes and confirming or discarding them), Moro periodically calls your Fleet Tracking API to get real-time location of nearby vehicles. This data is displayed to the user showing the nearest captain and estimated arrival time to help them make their decision.
GET https://your-api.com/fleets?latitude=24.713&longitude=46.675&radius=5
Timing: Happens after quote response and before user confirms or discards
Polling frequency: Every 5-10 seconds while session is active
Response: Array of nearby vehicles with position, ETA, driver info, and vehicle details
Step 8: Trip Completes or Gets Cancelled
When the passenger reaches their destination (trip completed) or if the trip is cancelled by driver/passenger, your system updates the trip status.
Possible outcomes: Trip completed successfully, Trip cancelled by passenger, Trip cancelled by driver, Trip cancelled by system
Step 9: Send Final Webhook to Moro
Send a final webhook notification with either trip.completed or trip.canceled event to inform Moro of the trip's final status.
Events: trip.completed (successful dropoff) or trip.canceled (cancellation with reason)
Required: Must include the original referenceId to close the session
- Quote API: Respond to
POST /quoterequests with pricing and availability - Deep Link: Handle incoming deep links from Moro with all trip parameters
- Webhooks: Send
trip.booked,trip.completed, andtrip.canceledevents - Fleet Tracking: Provide real-time fleet location via
GET /fleetsendpoint - referenceId: Always include this in webhook payloads to link events to user sessions
Key Concepts
Before implementing the integration, familiarize yourself with these core concepts:
Reference ID
Every quote request from Moro includes a unique referenceId (UUID format).
This identifier links the request to the user's session and must be included in all related webhook payloads.
Synchronous vs. Asynchronous Quotes
Your system can return quotes immediately (HTTP 200) or indicate processing is underway (HTTP 102).
With asynchronous quotes, Moro will poll your endpoint until results are ready.
Webhook Notifications
After a trip is booked, canceled, or completed, you send webhook notifications to Moro to keep both systems synchronized. Each webhook must be signed with your API secret for security.
Quote Request API
Moro sends quote requests to your system when a user requests a ride. Each request includes pickup/dropoff locations, desired time, and a unique reference ID for tracking.
Endpoint
referenceId is crucial for linking this request to subsequent webhook events.
Store it in your database and include it in all related notifications.
Minimum Required Request
At minimum, your system should handle these fields:
{
"referenceId": "moro_req_9f3b4e2a8c1d",
"origin": {
"position": {
"latitude": 24.713582,
"longitude": 46.675386
}
},
"destination": {
"position": {
"latitude": 24.954394,
"longitude": 46.712244
}
},
"dateScheduled": null
}
Note: When dateScheduled is null or omitted, it indicates an ASAP (as soon as possible) request.
Complete Request (Full Details)
This is a complete request with all available fields for location details:
{
"referenceId": "moro_req_9f3b4e2a8c1d",
"origin": {
"position": {
"latitude": 24.713582,
"longitude": 46.675386
},
"address": {
"displayAddress": "Kingdom Centre, Olaya St, Riyadh",
"buildingNumber": "123",
"streetName": "Olaya Street",
"city": "Riyadh",
"region": "Ar Riyad",
"postalCode": "12211",
"countryCode": "SA"
}
},
"destination": {
"position": {
"latitude": 24.954394,
"longitude": 46.712244
},
"address": {
"displayAddress": "Riyadh Airport Terminal 1",
"buildingNumber": "",
"streetName": "Airport Rd",
"city": "Riyadh",
"region": "Ar Riyad",
"postalCode": "11564",
"countryCode": "SA"
}
},
"dateScheduled": "2025-11-15T18:30:00Z"
}
Request Parameters
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
referenceId |
String (UUID) | Required | Unique identifier for this quote request generated by Moro. Must be stored and included in webhook payloads. | "moro_req_9f3b4e2a8c1d" |
| origin - Pickup location details | ||||
origin.position.latitude |
Number (Float) | Required | GPS latitude of pickup location. Ranges from -90 to 90 degrees. | 24.713582 |
origin.position.longitude |
Number (Float) | Required | GPS longitude of pickup location. Ranges from -180 to 180 degrees. | 46.675386 |
origin.address.displayAddress |
String | Optional | Human-readable address for the pickup location. Can be shown to driver. | "Kingdom Centre, Olaya St, Riyadh" |
origin.address.buildingNumber |
String | Optional | Building or house number of the location. | "123" |
origin.address.streetName |
String | Optional | Name of the street or road. | "Olaya Street" |
origin.address.city |
String | Optional | City or municipality name. | "Riyadh" |
origin.address.region |
String | Optional | State, province, or region name. | "Ar Riyad" |
origin.address.postalCode |
String | Optional | Postal or ZIP code for the area. | "12211" |
origin.address.countryCode |
String (ISO 3166-1 Alpha-2) | Optional | Two-letter country code following ISO 3166-1 standard. | "SA" (Saudi Arabia) |
| destination - Dropoff location details (same structure as origin) | ||||
destination.position.latitude |
Number (Float) | Required | GPS latitude of dropoff location. | 24.954394 |
destination.position.longitude |
Number (Float) | Required | GPS longitude of dropoff location. | 46.712244 |
destination.address.* |
String | Optional | All address fields (buildingNumber, streetName, city, region, postalCode, countryCode) are optional, same as origin. | See origin address fields |
| Timing | ||||
dateScheduled |
String (ISO 8601 DateTime) | Optional | Requested ride time in UTC format (ISO 8601). When null or undefined, this indicates an ASAP (as soon as possible) request. When provided, always include timezone information (Z for UTC). |
"2025-11-15T18:30:00Z" or null |
Quote Response Handling
Respond to quote requests based on your system's capabilities. You can either return quotes immediately or indicate processing is underway and have Moro poll for results.
Option A: Immediate Response (Synchronous)
If your system can generate quotes quickly, respond with HTTP 200 and quote data:
Minimum Response
At minimum, return these fields when a quote is available:
{
"quotes": [
{
"quoteId": "supp_quote_8a2c4f1e",
"quote": {
"currency": "SAR",
"total": 42.50
}
}
]
}
No Vehicles Available
If no vehicles are available, return HTTP 200 with an empty quotes array:
{
"quotes": []
}
Full Response with Multiple Quotes
Your system can return multiple quote options (different vehicle types, service levels, fleets, etc.):
{
"quotes": [
{
"quoteId": "supp_quote_8a2c4f1e",
"quote": {
"currency": "SAR",
"total": 42.50,
"subtotal": 40.00,
"tax": 2.50,
"serviceFee": 0.00
},
"vehicleType": "SEDAN",
"estimatedDuration": 15,
"estimatedDistance": 8.5,
"availableNow": true,
"fleet": {
"id": "fleet_premium_001",
"name": "Premium Fleet",
"description": "Standard sedan service"
},
"nearestFleet": {
"vehicleId": "veh_sedan_4521",
"vehicle": {
"make": "Toyota",
"model": "Camry",
"year": 2023,
"color": "White",
"licensePlate": "ABC 1234"
},
"driver": {
"id": "driver_8821",
"name": "Ahmed Al-Rashid",
"phone": "+966501234567",
"rating": 4.8
},
"position": {
"latitude": 24.710582,
"longitude": 46.672386
},
"eta": {
"minutes": 3,
"distance": 1.2
},
"state": "IDLE"
}
},
{
"quoteId": "supp_quote_8a2c4f1f",
"quote": {
"currency": "SAR",
"total": 68.75,
"subtotal": 65.00,
"tax": 3.75,
"serviceFee": 0.00
},
"vehicleType": "SUV",
"estimatedDuration": 15,
"estimatedDistance": 8.5,
"availableNow": true,
"fleet": {
"id": "fleet_premium_002",
"name": "Premium SUV Fleet",
"description": "Large vehicle for groups or luggage"
},
"nearestFleet": {
"vehicleId": "veh_suv_7832",
"vehicle": {
"make": "GMC",
"model": "Yukon",
"year": 2024,
"color": "Black",
"licensePlate": "XYZ 5678"
},
"driver": {
"id": "driver_9934",
"name": "Mohammed Al-Fahad",
"phone": "+966509876543",
"rating": 4.9
},
"position": {
"latitude": 24.715210,
"longitude": 46.678932
},
"eta": {
"minutes": 5,
"distance": 2.1
},
"state": "IDLE"
}
}
]
}
Option B: Deferred Response (Asynchronous)
If quotes require processing time (e.g., driver availability checks), respond with HTTP 102 Processing
and Moro will poll your endpoint for the final result.
Step 1: Initial Response
{
"quoteId": "supp_quote_8a2c4f1e",
"referenceId": "moro_req_9f3b4e2a8c1d",
"message": "Quote calculation in progress"
}
Step 2: Moro Polls Your Endpoint
Step 3: Return Final Quote
{
"quotes": [
{
"quoteId": "supp_quote_8a2c4f1e",
"quote": {
"currency": "SAR",
"total": 42.50,
"subtotal": 40.00,
"tax": 2.50,
"serviceFee": 0.00
},
"vehicleType": "SEDAN",
"estimatedDuration": 15,
"estimatedDistance": 8.5,
"availableNow": true,
"fleet": {
"id": "fleet_premium_001",
"name": "Premium Fleet",
"description": "Standard sedan service"
}
}
]
}
404 Not Found if the quote expires or becomes unavailable.
Response Parameters
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
quotes[] |
Array of Objects | Required | Array of available quotes. Can be empty if no vehicles are available. In HTTP 102 response, this is not required. | [{...}] |
| Quote Object Fields (inside quotes array) | ||||
quoteId |
String | Required | Your internal unique identifier for this quote. Used when Moro needs to reference this quote in future requests. | "supp_quote_8a2c4f1e" |
| quote - Pricing information | ||||
quote.currency |
String (ISO 4217) | Required | Three-letter currency code. Currently only "SAR" (Saudi Arabian Riyal) is supported. |
"SAR" |
quote.total |
Number (Float) | Required | Total price in the specified currency. Must be greater than 0. This is the amount Moro will display to the user. | 42.50 |
quote.subtotal |
Number (Float) | Optional | Base fare before taxes and fees. Helps with transparency in pricing breakdown. | 40.00 |
quote.tax |
Number (Float) | Optional | Tax amount (typically VAT in Saudi Arabia at 15%). Part of the total. | 2.50 |
quote.serviceFee |
Number (Float) | Optional | Service or platform fee. Part of the total. | 0.00 |
| Vehicle & Trip Information | ||||
vehicleType |
String | Optional | Type of vehicle for this quote. Helps user understand the service level. Examples: SEDAN, SUV, VAN, PREMIUM, ECONOMY | "SEDAN" |
estimatedDuration |
Number (Integer) | Optional | Estimated trip duration in minutes, from pickup to dropoff. | 15 |
estimatedDistance |
Number (Float) | Optional | Estimated trip distance in kilometers. | 8.5 |
availableNow |
Boolean | Optional | Whether this vehicle/service is available for immediate pickup or if there's a wait. | true |
| fleet - Fleet/Company information | ||||
fleet.id |
String | Optional | Unique identifier for the fleet/service provider within your system. Helps differentiate between different brands or services you operate. | "fleet_premium_001" |
fleet.name |
String | Optional | Display name of the fleet. This will be shown to users in the app. | "Premium Fleet" |
fleet.description |
String | Optional | Brief description of the fleet or service level. Helps users understand the quality/type of service. | "Standard sedan service" |
| nearestFleet - Nearest available fleet ETA information | ||||
nearestFleet.vehicleId |
String | Optional | Unique identifier for the nearest available vehicle in your system. | "veh_sedan_4521" |
nearestFleet.vehicle.make |
String | Optional | Vehicle manufacturer/brand name. | "Toyota" |
nearestFleet.vehicle.model |
String | Optional | Vehicle model name. | "Camry" |
nearestFleet.vehicle.year |
Number (Integer) | Optional | Manufacturing year of the vehicle. | 2023 |
nearestFleet.vehicle.color |
String | Optional | Vehicle color for easy identification. | "White" |
nearestFleet.vehicle.licensePlate |
String | Optional | Vehicle license plate number. | "ABC 1234" |
nearestFleet.driver.id |
String | Optional | Unique identifier for the driver in your system. | "driver_8821" |
nearestFleet.driver.name |
String | Optional | Driver's full name. | "Ahmed Al-Rashid" |
nearestFleet.driver.phone |
String | Optional | Driver's phone number in international format. | "+966501234567" |
nearestFleet.driver.rating |
Number (Float) | Optional | Driver's average rating (0.0 to 5.0). | 4.8 |
nearestFleet.position.latitude |
Number (Float) | Optional | Current GPS latitude of the nearest vehicle. | 24.710582 |
nearestFleet.position.longitude |
Number (Float) | Optional | Current GPS longitude of the nearest vehicle. | 46.672386 |
nearestFleet.eta.minutes |
Number (Integer) | Optional | Estimated time of arrival to pickup location in minutes. | 3 |
nearestFleet.eta.distance |
Number (Float) | Optional | Distance from vehicle's current position to pickup location in kilometers. | 1.2 |
nearestFleet.state |
String (Enum) | Optional | Current state of the vehicle. Possible values: IDLE (available), EN_ROUTE (on the way to pickup), ON_TRIP (currently on a trip), OFFLINE (not available). |
"IDLE" |
| HTTP 102 Processing Response Only | ||||
message |
String | Optional | Human-readable message explaining the current status. Only used in HTTP 102 responses. | "Quote calculation in progress" |
Fleet Tracking API
The Fleet Tracking API allows Moro to retrieve real-time information about available fleets near a specific location. This endpoint is designed to be called frequently for live tracking and availability updates.
Get Nearby Fleets
Retrieve all available fleets within a specified radius of a location.
Query Parameters - Nearby Fleets
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
latitude |
Number (Float) | Required | GPS latitude of the center point to search from. | 24.713582 |
longitude |
Number (Float) | Required | GPS longitude of the center point to search from. | 46.675386 |
radius |
Number (Float) | Optional | Search radius in kilometers. Defaults to 5km if not specified. Maximum 50km. | 5 |
vehicleType |
String | Optional | Filter by vehicle type (SEDAN, SUV, VAN, etc.). If omitted, returns all types. | SEDAN |
state |
String | Optional | Filter by vehicle state (IDLE, EN_ROUTE, ON_TRIP, OFFLINE). If omitted, returns IDLE vehicles only. | IDLE |
Nearby Fleets Response Example
{
"fleets": [
{
"vehicleId": "veh_sedan_4521",
"vehicle": {
"make": "Toyota",
"model": "Camry",
"year": 2023,
"color": "White",
"licensePlate": "ABC 1234"
},
"driver": {
"id": "driver_8821",
"name": "Ahmed Al-Rashid",
"phone": "+966501234567",
"rating": 4.8
},
"position": {
"latitude": 24.710582,
"longitude": 46.672386
},
"eta": {
"minutes": 3,
"distance": 1.2
},
"state": "IDLE",
"vehicleType": "SEDAN",
"fleetInfo": {
"id": "fleet_premium_001",
"name": "Premium Fleet"
}
},
{
"vehicleId": "veh_suv_7832",
"vehicle": {
"make": "GMC",
"model": "Yukon",
"year": 2024,
"color": "Black",
"licensePlate": "XYZ 5678"
},
"driver": {
"id": "driver_9934",
"name": "Mohammed Al-Fahad",
"phone": "+966509876543",
"rating": 4.9
},
"position": {
"latitude": 24.715210,
"longitude": 46.678932
},
"eta": {
"minutes": 5,
"distance": 2.1
},
"state": "IDLE",
"vehicleType": "SUV",
"fleetInfo": {
"id": "fleet_premium_002",
"name": "Premium SUV Fleet"
}
}
],
"total": 2,
"timestamp": "2025-11-15T18:30:00Z"
}
Fleet Response Parameters
| Field | Type | Required | Description | Example |
|---|---|---|---|---|
fleets[] |
Array of Objects | Required | Array of available fleet vehicles. Empty array if none are available. | [{...}] |
total |
Number (Integer) | Optional | Total number of fleets returned. | 2 |
timestamp |
String (ISO 8601 DateTime) | Optional | Timestamp when this data was generated in UTC format. | "2025-11-15T18:30:00Z" |
| Fleet Object Fields | ||||
vehicleId |
String | Required | Unique identifier for this vehicle in your system. | "veh_sedan_4521" |
vehicle.make |
String | Required | Vehicle manufacturer/brand name. | "Toyota" |
vehicle.model |
String | Required | Vehicle model name. | "Camry" |
vehicle.year |
Number (Integer) | Optional | Manufacturing year of the vehicle. | 2023 |
vehicle.color |
String | Optional | Vehicle color for easy identification. | "White" |
vehicle.licensePlate |
String | Optional | Vehicle license plate number. | "ABC 1234" |
driver.id |
String | Required | Unique identifier for the driver in your system. | "driver_8821" |
driver.name |
String | Required | Driver's full name. | "Ahmed Al-Rashid" |
driver.phone |
String | Optional | Driver's phone number in international format. | "+966501234567" |
driver.rating |
Number (Float) | Optional | Driver's average rating (0.0 to 5.0). | 4.8 |
position.latitude |
Number (Float) | Required | Current GPS latitude of the vehicle. | 24.710582 |
position.longitude |
Number (Float) | Required | Current GPS longitude of the vehicle. | 46.672386 |
position.heading |
Number (Integer) | Optional | Vehicle heading/direction in degrees (0-360). 0 is North, 90 is East, etc. | 145 |
position.speed |
Number (Float) | Optional | Current speed in km/h. | 0 |
eta.minutes |
Number (Integer) | Optional | Estimated time of arrival to the search location in minutes. | 3 |
eta.distance |
Number (Float) | Optional | Distance from vehicle to search location in kilometers. | 1.2 |
state |
String (Enum) | Required | Current state of the vehicle. Possible values: IDLE, EN_ROUTE, ON_TRIP, OFFLINE. |
"IDLE" |
vehicleType |
String | Required | Type of vehicle. Examples: SEDAN, SUV, VAN, PREMIUM, ECONOMY. | "SEDAN" |
fleetInfo.id |
String | Required | Unique identifier for the fleet/service provider. | "fleet_premium_001" |
fleetInfo.name |
String | Required | Display name of the fleet. | "Premium Fleet" |
fleetInfo.description |
String | Optional | Brief description of the fleet or service level. | "Standard sedan service" |
lastUpdated |
String (ISO 8601 DateTime) | Optional | Timestamp when this vehicle data was last updated. | "2025-11-15T18:30:00Z" |
Webhook Notifications
After Moro books a trip with your supplier, your system must notify Moro of trip status changes (booked, canceled, completed) via webhook notifications.
Webhook Endpoint
Required Headers
X-API-Key: Your supplier API key (provided by Moro)X-Signature: HMAC-SHA256 signature of the request bodyContent-Type:application/json
HMAC-SHA256(secret, body) in lowercase hex format.
Moro will verify this signature to ensure the webhook is authentic.
Webhook Payload Example
{
"event": "trip.booked",
"data": {
"referenceId": "moro_req_9f3b4e2a8c1d",
"tripId": "trip_8a2c4f1e",
"pickup": {
"latitude": 24.713582,
"longitude": 46.675386,
"address": "Kingdom Centre, Olaya St, Riyadh"
},
"dropoff": {
"latitude": 24.954394,
"longitude": 46.712244,
"address": "Riyadh Airport Terminal 1"
},
"scheduledAt": "2025-11-15T18:30:00Z",
"passenger": {
"name": "Mohammed Al-Saud",
"phone": "+966501234567"
},
"quoteTotal": 42.50,
"currency": "SAR"
}
}
Supported Events
| Event | Description | When to Send |
|---|---|---|
trip.booked |
Trip confirmed and driver assigned | Immediately after driver accepts |
trip.canceled |
Trip canceled by driver, passenger, or system | When cancellation is confirmed |
trip.completed |
Passenger successfully dropped off | After passenger confirms arrival |
Best Practices & Error Handling
HTTP Status Codes
- 200 OK – Quote processed successfully (even if no vehicles available)
- 102 Processing – Quote calculation in progress; Moro will poll again
- 400 Bad Request – Invalid request (e.g., missing required fields)
- 404 Not Found – Quote ID not found or has expired during polling
- 500 Server Error – Internal server error; Moro will retry
Common Mistakes to Avoid
total: 0 – return an empty quotes array instead if no vehicles are available.
referenceId from the quote request.
Without it, Moro cannot match the event to the correct user session.
Support & Contact
Need help with integration? Contact our support team:
support@moro.sa
Expected response time: 24 hours during business days