Third-Party Delivery Dispatch — webhook specs & order flow
When a customer selects "Delivery" on the Concessions page, the app sends a POST request to your webhook URL with the full order details. Your webhook must respond with a delivery fee and estimated delivery time. The app collects quotes from all active providers in parallel and selects the cheapest one.
POSTAuthorization: Bearer YOUR_API_KEYapplication/json| Field | Type | Description |
|---|---|---|
| dispatch_id | string | Unique ID for this quote request. Use for idempotency/logging. |
| timestamp | string (ISO) | When the request was sent. |
| restaurant | object | Pickup origin: {name, address, city, state, phone} |
| customer | object | Customer info: {name, phone} |
| delivery_address | string | Full delivery destination address as entered by the customer. |
| delivery_lat | number|null | Destination latitude (optional, from GPS). |
| delivery_lng | number|null | Destination longitude (optional, from GPS). |
| items | array | Order line items: [{name, qty, price, modifiers}] |
| subtotal | number | Order subtotal in USD (before fees/tax). |
| processing_fee | number | Processing fee applied (for your info). |
| service_fee | number | Service fee applied (for your info). |
| tax | number | Sales tax applied (for your info). |
| notes | string | Customer delivery instructions (e.g. "Leave at front door"). |
POST /your-webhook-endpoint HTTP/1.1
Host: your-server.com
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY
{
"dispatch_id": "abc123-def456",
"timestamp": "2026-09-22T14:52:00.000Z",
"restaurant": {
"name": "Brownsville Drive-In",
"address": "123 Route 40",
"city": "Brownsville",
"state": "PA",
"phone": "724-555-0100"
},
"customer": {
"name": "Jane Smith",
"phone": "724-555-0123"
},
"delivery_address": "456 Oak Ave, Uniontown, PA 15401",
"delivery_lat": 39.9000,
"delivery_lng": -79.7000,
"items": [
{
"name": "Cheeseburger",
"qty": 2,
"price": 8.50,
"modifiers": []
},
{
"name": "Large Fries (Extra Salt)",
"qty": 1,
"price": 5.00,
"modifiers": [
{
"name": "Seasoning",
"options": [{ "name": "Extra Salt", "price": 0 }]
}
]
}
],
"subtotal": 22.00,
"processing_fee": 0.66,
"service_fee": 1.10,
"tax": 1.32,
"notes": "Leave at front door"
}| Field | Type | Description |
|---|---|---|
| delivery_fee | number | Required. Delivery fee in USD (e.g. 5.99). Must be > 0. Also accepts deliveryFee or fee. |
| estimated_minutes | integer | Required. Estimated delivery time in minutes (e.g. 45). Also accepts estimatedMinutes, eta_minutes, or eta. |
HTTP/1.1 200 OK
Content-Type: application/json
{
"delivery_fee": 5.99,
"estimated_minutes": 45
}dispatch_id to deduplicate — the app may retry the same request.delivery_fee. ETA is shown to the customer but doesn't affect selection.import express from "express";
const app = express();
app.use(express.json());
app.post("/delivery-quote", async (req, res) => {
// Verify the API key
const auth = req.headers.authorization;
if (auth !== `Bearer ${process.env.PROVIDER_API_KEY}`) {
return res.status(401).json({ error: "Unauthorized" });
}
const { delivery_address, items, subtotal } = req.body;
// Calculate your delivery fee based on distance, order size, etc.
const delivery_fee = await calculateFee(delivery_address, subtotal);
const estimated_minutes = await estimateTime(delivery_address);
// Must respond within 8 seconds
res.json({
delivery_fee: delivery_fee, // USD, e.g. 5.99
estimated_minutes: estimated_minutes // integer minutes, e.g. 45
});
});
app.listen(3000);The customer pays the drive-in directly via Stripe (the delivery fee + tip is included in the total). After payment, the delivery fee + tip is transferred from the drive-in's Stripe account to the directory's connected Stripe account for disbursement to you (the provider). You do not collect payment from the customer — the platform handles it.
delivery_fee you quoted + the customer's tip. The drive-in's food subtotal, fees, and tax are kept by the drive-in.