This guide walks you through the exact sequence of API calls needed to add Yonne delivery to your checkout — from capturing the customer’s location and displaying a real-time fee, to dispatching a rider the moment they pay.
The complete flow
Customer adds item to cart
│
▼
┌─────────────────────────┐
│ Step 1: VALIDATE │ On startup / merchant onboarding
│ GET /validate │ Confirm key is active + pickup is set
└──────────┬──────────────┘
│
▼
┌─────────────────────────┐
│ Step 2: QUOTE │ Request customer location via browser
│ POST /quote │ Geolocation API → get delivery_fee + ETA
└──────────┬──────────────┘
│
▼
Customer reviews fee
and confirms order
│
▼
┌─────────────────────────┐
│ Step 3: COLLECT │ Your payment gateway (Paychangu, Stripe, etc.)
│ Customer pays │ Capture payment including delivery_fee
└──────────┬──────────────┘
│
▼
┌─────────────────────────┐
│ Step 4: CREATE ORDER │ Server-side, after payment succeeds
│ POST /create-order │ Dispatch the rider with Idempotency-Key
└──────────┬──────────────┘
│
▼
Return tracking_link
to customer confirmation page
Step 1 — Validate on startup
Before your checkout can work, confirm your API key is active and your merchant pickup address is configured. Call this once on server startup (or when onboarding a new merchant).
curl --request GET "https://api.yonne.app/api/v1/external/validate" \
--header "X-API-Key: yonne_live_xxxxxxxxx"
Success response:
{
"success": true,
"balance": 125000,
"currency": "MWK",
"hasPickup": true,
"pickupAddress": "Area 3, Lilongwe",
"pickupLatitude": -13.9626,
"pickupLongitude": 33.7741,
"environment": "live"
}
If hasPickup is false, stop here. Go to your Yonne dashboard and configure the pickup address. Order creation will fail until this is set.
Step 2 — Capture the customer’s location, then quote
Yonne calculates the delivery fee based on the real distance between your pickup point and the customer’s location. A typed address is not sufficient — you must send precise GPS coordinates (delivery_lat, delivery_lng).
Get the customer’s coordinates first, using the browser Geolocation API on your frontend:
// Frontend — request browser location, then POST to your own checkout API
navigator.geolocation.getCurrentPosition(
(position) => {
const { latitude, longitude } = position.coords;
fetchDeliveryQuote(latitude, longitude);
},
(error) => {
// Fall back to asking the customer to enter coordinates manually,
// or use a geocoding service to convert a typed address to lat/lng
console.error("Location access denied:", error.message);
}
);
Once you have the coordinates, call POST /api/v1/external/quote from your server and display the returned delivery_fee and eta on the checkout page.
curl --request POST "https://api.yonne.app/api/v1/external/quote" \
--header "X-API-Key: yonne_live_xxxxxxxxx" \
--header "Content-Type: application/json" \
--data '{
"delivery_lat": -13.9831,
"delivery_lng": 33.7812,
"product_type": "Package",
"weight_estimate": "0-5kg",
"delivery_speed": "standard"
}'
Success response:
{
"success": true,
"price": 7350,
"delivery_fee": 7350,
"currency": "MWK",
"eta": "24m",
"eta_minutes": 24,
"service_id": "bike_std",
"suggested_vehicle_class": "BIKE",
"required_weight_kg": 5,
"distance_km": 3.9,
"environment": "live"
}
What to do with this response:
- Display
delivery_fee (in MWK) and eta on your checkout page.
- Store
delivery_fee in your checkout session — you’ll pass it unchanged to create-order.
- Optionally show
suggested_vehicle_class (“A bike will deliver your order”).
Step 3 — Collect payment
This step happens entirely in your existing payment flow. Yonne is not involved.
Ensure the total your payment gateway charges includes the delivery_fee from the quote. When the payment succeeds, proceed to Step 4.
Never create a Yonne order before the payment is confirmed. Create it in your payment success webhook or confirmation callback, not in the checkout page render.
Step 4 — Create the order (dispatch the rider)
Call POST /api/v1/external/create-order server-side, immediately after payment is confirmed. The Idempotency-Key header is required — generate it from your internal order ID so retries are safe.
curl --request POST "https://api.yonne.app/api/v1/external/create-order" \
--header "X-API-Key: yonne_live_xxxxxxxxx" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: order-WEB-100245-attempt-1" \
--data '{
"delivery_address": "Mchesi, Lilongwe",
"receiver_name": "Jane Banda",
"receiver_phone": "+265991234567",
"delivery_fee": 7350,
"delivery_lat": -13.9831,
"delivery_lng": 33.7812,
"item_name": "Printer cartridge",
"product_type": "Package",
"weight_estimate": "0-5kg",
"merchant_reference_id": "WEB-100245",
"metadata": {
"channel": "woocommerce",
"checkout_session_id": "cs_abc123"
}
}'
Success response:
{
"success": true,
"tracking_id": "YON-TRK-123456",
"order_id": "ORD-123456",
"status": "searching",
"tracking_link": "https://yonne.app/track/YON-TRK-123456",
"delivery_fee": 7350,
"wallet_balance": 117650,
"currency": "MWK",
"environment": "live"
}
What to do with this response:
- Save
order_id — use it for cancellations, status checks, and support lookups.
- Save
tracking_id — use it in customer-facing tracking links.
- Show
tracking_link on the order confirmation page so the customer can track in real time.
Handling create-order failures
402 Insufficient Funds
{
"success": false,
"error": "Insufficient Funds",
"message": "Wallet balance is insufficient for this delivery. Please top up.",
"balance": 1500,
"required": 7350
}
The customer’s payment has already gone through, but your Yonne wallet is empty. Handle this gracefully:
- Flag the order internally as
dispatch_failed_insufficient_funds.
- Alert your operations team immediately — they need to top up the wallet.
- After topping up, retry create-order with the same
Idempotency-Key.
- Do not leave the customer without a response — send them an “order confirmed, we’ll dispatch soon” message.
See Wallet Management for the full top-up flow.
422 ERR_NO_CAPACITY_AVAILABLE
{
"success": false,
"error": "ERR_NO_CAPACITY_AVAILABLE",
"message": "No online rider has sufficient vehicle capacity for this package weight.",
"required_weight_kg": 95
}
No rider is available right now. Do not silently downgrade the vehicle class. Flag the order as dispatch_failed_no_capacity and retry later.
Step 5 — Show the customer their tracking link
After create-order succeeds, surface tracking_link on the order confirmation page:
<p>Your order is confirmed! <a href="{{ tracking_link }}">Track your delivery →</a></p>
From this point, Yonne will push status updates to your webhook endpoint as the order progresses. See Real-time Tracking to wire up the full tracking experience.
Integration checklist
Before going live with this flow: