3PD Delivery Guide

Third-Party Delivery Dispatch — webhook specs & order flow

Quote Webhook Specification

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.

8s timeout
Bearer auth
JSON
10-min quote lock

1. Endpoint & Authentication

Method:POST
URL:The webhook URL you configure in the Delivery Providers admin page
Auth:Authorization: Bearer YOUR_API_KEY
Content-Type:application/json
Important: The API key is the same value you entered in the "API Key" field when creating your Delivery Provider record. The app sends it as a Bearer token. Verify it on every request to authenticate the caller.

2. Request Payload (JSON)

FieldTypeDescription
dispatch_idstringUnique ID for this quote request. Use for idempotency/logging.
timestampstring (ISO)When the request was sent.
restaurantobjectPickup origin: {name, address, city, state, phone}
customerobjectCustomer info: {name, phone}
delivery_addressstringFull delivery destination address as entered by the customer.
delivery_latnumber|nullDestination latitude (optional, from GPS).
delivery_lngnumber|nullDestination longitude (optional, from GPS).
itemsarrayOrder line items: [{name, qty, price, modifiers}]
subtotalnumberOrder subtotal in USD (before fees/tax).
processing_feenumberProcessing fee applied (for your info).
service_feenumberService fee applied (for your info).
taxnumberSales tax applied (for your info).
notesstringCustomer 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"
}

3. Response (JSON — 200 OK)

FieldTypeDescription
delivery_feenumberRequired. Delivery fee in USD (e.g. 5.99). Must be > 0. Also accepts deliveryFee or fee.
estimated_minutesintegerRequired. 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
}

4. Rules & Error Handling

  • 8-second timeout. If your webhook doesn't respond within 8 seconds, the quote is marked as "timeout" and excluded from selection.
  • delivery_fee must be > 0. A fee of 0 or missing is treated as an error (provider declined).
  • Non-200 responses are treated as errors. Include an error message in the body if possible.
  • Idempotency: Use dispatch_id to deduplicate — the app may retry the same request.
  • Cheapest wins. The app selects the provider with the lowest delivery_fee. ETA is shown to the customer but doesn't affect selection.
  • 10-minute lock. Once selected, the quote is locked for 10 minutes. If the customer doesn't check out in time, they must request a new quote.

5. Example Implementation (Node.js / Express)

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);

6. Payment & Disbursement

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.

What you receive: The delivery_fee you quoted + the customer's tip. The drive-in's food subtotal, fees, and tax are kept by the drive-in.