Supplier Integration Guide

Phase 1: Quote Requests, Response Handling & Webhook Notifications

On This Page

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.

1. User Requests Ride Pickup & Dropoff Request MORO PLATFORM Mobility Aggregator Sends parallel quote requests Supplier A Quote: 42.50 SAR Supplier B Quote: 38.00 SAR Supplier C No vehicles POST /quote 3. User Compares Quotes Supplier A: 42.50 SAR Supplier B: 38.00 SAR ✓ Display 7. Fleet Tracking While user decides GET /fleets?lat=...&lng=... Show nearest Real-time data 4. User Selects Deep Link Redirect supplier://book?... Opens Supplier App 5. User Confirms In Supplier App Trip Confirmed 6. Webhook: trip.booked POST /webhooks/supplier-events Supplier → Moro Notify 8. Trip Ends Completed or Cancelled 9. Final Webhook trip.completed / trip.canceled Supplier → Moro

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.

Deep Link Format:
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 3
  • referenceId - Moro's session reference (UUID) - CRITICAL for webhooks
  • pickupLat & pickupLng - Pickup coordinates
  • dropoffLat & dropoffLng - Dropoff coordinates
  • promoCode - Promotional discount code (if applicable)
  • affiliateCode - Affiliate/partner tracking code
  • passengerName - Passenger's name for driver reference
  • passengerPhone - Passenger's phone number
  • scheduledTime - 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.

Webhook Endpoint:
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

Key Integration Points
  • Quote API: Respond to POST /quote requests with pricing and availability
  • Deep Link: Handle incoming deep links from Moro with all trip parameters
  • Webhooks: Send trip.booked, trip.completed, and trip.canceled events
  • Fleet Tracking: Provide real-time fleet location via GET /fleets endpoint
  • 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

POST https://your-supply-api.com/quote
Important: Store the Reference ID
The 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:

Minimum Request - HTTP POST (ASAP Ride)
{
  "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:

Full Request - HTTP POST
{
  "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:

Minimum Response - HTTP 200 OK
{
  "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:

No Quotes Response - HTTP 200 OK
{
  "quotes": []
}

Full Response with Multiple Quotes

Your system can return multiple quote options (different vehicle types, service levels, fleets, etc.):

Complete Response - HTTP 200 OK
{
  "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

Response - HTTP 102 PROCESSING
{
  "quoteId": "supp_quote_8a2c4f1e",
  "referenceId": "moro_req_9f3b4e2a8c1d",
  "message": "Quote calculation in progress"
}

Step 2: Moro Polls Your Endpoint

GET https://your-supply-api.com/quote/supp_quote_8a2c4f1e

Step 3: Return Final Quote

Response - HTTP 200 OK
{
  "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"
      }
    }
  ]
}
Polling Timeout
Moro will poll your endpoint every 1-2 seconds for a maximum of 30 seconds. Return 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.

Frequent Polling
This endpoint is optimized for frequent calls (every 5-10 seconds) to provide real-time fleet tracking. Ensure your implementation is performant and uses caching where appropriate.

Get Nearby Fleets

Retrieve all available fleets within a specified radius of a location.

GET https://your-supply-api.com/fleets?latitude=24.713582&longitude=46.675386&radius=5

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

Response - HTTP 200 OK
{
  "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"
Performance Optimization Tips
We frequently call this API to update our client fleet, so please follow optimization approaches to ensure fast response times and efficient resource usage.

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

POST https://api.moro.sa/v1/webhooks/supplier-events

Required Headers

Security: Always Sign Webhooks
Compute the signature using: 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
{
  "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

Common Mistakes to Avoid

Don't Return Zero Prices
Always return actual quote amounts. Never set total: 0 – return an empty quotes array instead if no vehicles are available.
Always Include referenceId
Every webhook payload must include the original 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