Skip to main content
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:
  1. Flag the order internally as dispatch_failed_insufficient_funds.
  2. Alert your operations team immediately — they need to top up the wallet.
  3. After topping up, retry create-order with the same Idempotency-Key.
  4. 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.
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:
  • GET /validate is called on startup and hasPickup is confirmed
  • Customer location is collected via the browser Geolocation API (or geocoding) before calling /quote
  • delivery_fee from the quote is passed unchanged into create-order (not recalculated)
  • Idempotency-Key is generated from your internal order ID on every create-order call
  • order_id and tracking_id are saved to your database
  • 402 and 422 errors alert your ops team rather than failing silently
  • Webhook receiver is live to receive status updates without polling