Guide to DoorDash Drive Webhooks: Features and Best Practices
DoorDash Drive webhooks tell your application where a delivery is. When a Dasher accepts an order, arrives at the store, picks up, drops off, or the delivery is cancelled or returned, DoorDash sends the update to your endpoint. That is what powers order status pages, customer notifications and live maps for businesses that use DoorDash's Dasher network to fulfil their own orders.
This guide covers how Drive webhooks are configured and authenticated, the delivery, return and tracking events, what the payload contains at each stage, how DoorDash retries, and the practices that make a Drive receiver dependable.
What are DoorDash Drive webhooks?
Drive webhooks are configured once per environment in the DoorDash Developer Portal. Sandbox and Production each support exactly one endpoint, and that endpoint receives every event for every delivery in the environment. Each request is a JSON object describing the delivery as it stands when the webhook is sent, with an event_name field saying what just happened.
DoorDash does not sign webhooks. The Portal offers two ways to protect your endpoint. Basic Auth sends a value you choose in the Authorization header. Despite the name, DoorDash sends exactly the text you enter, which is often a bare token rather than a base64 username:password. OAuth has DoorDash fetch a token from a URL you provide, using a client ID and secret.
This guide covers the Drive API. Drive (classic) has its own webhook reference.
DoorDash Drive webhook features
| Feature | Details |
|---|---|
| Configuration | Developer Portal > Webhooks > + for Sandbox or Production |
| Endpoints | One per environment. Production requires production access first |
| Transport | HTTPS, JSON body |
| Authentication | Basic Auth (your text, sent verbatim as Authorization) or OAuth (client ID, secret, token URL, optional scope) |
| Signature and timestamp | None |
| Event field | event_name, at the top level |
| Payload | The delivery's current fields. Fields that are empty or not yet known are omitted |
| Time and money | Times are ISO-8601 in UTC. Amounts are in cents |
| Retries | Each event is sent up to 3 times. Anything other than 200 OK, or no response, is retried |
| Tracking events | Opt-in by support request. Dasher location every 30 seconds |
| Refund webhooks | By approval from the DoorDash Integrations team |
| Testing | The Sandbox delivery simulator sends a webhook for every change |
| Source IPs | Not published |
Common events
| Group | Events | Sent when |
|---|---|---|
| All deliveries | DASHER_CONFIRMED, DASHER_, DASHER_PICKED_UP, DASHER_, DASHER_DROPPED_OFF, DELIVERY_CANCELLED | A Dasher accepts, arrives at pickup, picks up, arrives at dropoff, completes the delivery, or the delivery is cancelled |
| Return to pickup | DELIVERY_, DASHER_, DELIVERY_RETURNED | A Dasher could not deliver and is returning, arrives back at pickup, or completes the return. A failed return is DELIVERY_CANCELLED with cancellation_reason failed_to_return |
| Pre-staged batches | DELIVERY_BATCHED | A delivery is assigned to a batch, identified by force_batch_id, before a Dasher is assigned |
| Tracking (opt-in) | dasher_enroute_to_pickup, dasher_, dasher_enroute_to_return | Every 30 seconds, starting 15 minutes before pickup or dropoff, with the Dasher's location |
Note the casing: delivery events are upper case and the tracking events are lower case. Cancellations carry a cancellation_reason such as cancel_by_merchant, no_available_dashers, store_closed or wrong_delivery_address.
A payload is a flat delivery object. Here is DoorDash's DASHER_DROPPED_OFF example, trimmed:
{
"created_at": "2022-02-01T23:18:22.791883Z",
"event_name": "DASHER_DROPPED_OFF",
"external_delivery_id": "c19a5d37-e457-4247-9a67-921ec0134125",
"dasher_id": 123212,
"dasher_name": "John D.",
"dasher_location": { "lat": 43.333333333, "lng": -79.333333333 },
"pickup_address": "1000 4th Avenue, Seattle, WA 98104",
"dropoff_address": "1201 3rd Avenue, Seattle, WA 98101",
"order_value": 5555,
"currency": "USD",
"updated_at": "2022-02-01T23:18:22.791883Z",
"pickup_time_actual": "2022-02-01T23:17:20.521249Z",
"dropoff_time_actual": "2022-02-01T23:18:22.541773Z",
"fee": 975,
"tip": 230,
"support_reference": "1343593362",
"tracking_url": "https://doordash.com/drive/portal/track/53904a0b-18cd-4308-b6dc-1d83932d7990",
"contactless": false
}
external_delivery_id is the ID you supplied when you created the delivery, so it is the key that ties a webhook back to your order. Fields appear as they become known: pickup_time_actual only from DASHER_PICKED_UP onward, dropoff_time_actual and the verification image URLs from DASHER_DROPPED_OFF, and return_address from DELIVERY_. DoorDash tells you to check that a field is present before reading it.
Refund webhooks look different. They are sent only once DoorDash has approved you for them, and the body is an event_data object with reason, amount, currency_code and amount_details, with no event_name.
Inspect DoorDash Drive webhooks as they arrive. Point your Sandbox webhook at a Hookdeck Inspect URL and run the delivery simulator to see every event, with no account or setup required.
Setting up DoorDash Drive webhooks
Generate a long random token first, for example with openssl rand -hex 32.
- In the Developer Portal, click Webhooks in the left navigation.
- Click + for the environment you want, Sandbox or Production. Production needs production access, and each environment has only one endpoint.
- Enter your HTTPS endpoint URL.
- Choose Basic Auth and enter the exact text you want in the
Authorizationheader, for exampleBearer <your token>. - Click Configure Endpoint.
- In Sandbox, use the delivery simulator to create a delivery and advance it through its stages. Each change sends a webhook.
To put Hookdeck in front, create a Hookdeck source with the DoorDash Drive type and use its URL in step 3. The source has one field, Authorization Header: paste the value exactly as you entered it in the Developer Portal, including any Bearer prefix. The source compares the whole header, so a missing prefix on either side means no match. It accepts POST. The Portal's OAuth option is not something this source type can verify, so use Basic Auth with Hookdeck.
Because each environment has one endpoint, use separate Hookdeck sources, or at least separate tokens, for Sandbox and Production.
Securing DoorDash Drive webhooks
Compare the whole Authorization header with the value you entered in the Portal, in constant time. Do not decode it as HTTP Basic or strip a prefix: DoorDash sends your text unchanged.
const crypto = require('crypto');
const express = require('express');
// Exactly what you typed in the Developer Portal, e.g. "Bearer 7c1e...".
const EXPECTED = process.env.DOORDASH_DRIVE_AUTHORIZATION;
function safeEqual(received, expected) {
const a = Buffer.from(received, 'utf8');
const b = Buffer.from(expected, 'utf8');
// timingSafeEqual throws on unequal lengths, so check first.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
const app = express();
app.post('/webhooks/doordash', express.raw({ type: '*/*' }), (req, res) => {
const received = req.get('authorization');
if (!EXPECTED || typeof received !== 'string' || !safeEqual(received, EXPECTED)) {
return res.sendStatus(401);
}
let delivery;
try {
delivery = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
if (delivery.event_data) {
// Refund webhook: no event_name.
} else {
// Route on delivery.event_name; key on delivery.external_delivery_id.
}
// DoorDash retries anything other than 200 OK, so send exactly 200.
res.sendStatus(200);
});
The same check in Python:
import hmac
import os
EXPECTED = os.environ.get("DOORDASH_DRIVE_AUTHORIZATION", "")
def verify_doordash_drive_request(headers) -> bool:
if not EXPECTED:
return False # never accept when unconfigured
received = headers.get("authorization")
if not received:
return False
# Compare bytes: compare_digest raises TypeError on a non-ASCII str.
return hmac.compare_digest(received.encode("utf-8"), EXPECTED.encode("utf-8"))
A static Authorization value proves the sender knows the secret. It does not cover the body, and there is no timestamp, so a captured request can be replayed and still pass. Serve the endpoint over HTTPS only, use a long random value, and rotate it if it might have leaked.
Make DoorDash Drive webhooks production-ready. Hookdeck Event Gateway checks the
Authorizationvalue you configure, queues every delivery update durably, and retries delivery to your service long after DoorDash's three attempts are spent.
DoorDash Drive webhook limitations and pain points
"Basic Auth" is not HTTP Basic
The Problem: Code that decodes the Authorization header as base64 username:password rejects every request, or accepts nothing it can parse.
Why It Happens: The Portal's Basic Auth option sends whatever text you enter, verbatim. Many integrations enter a bare token or a Bearer value, which is not HTTP Basic at all.
Workarounds:
- Store the exact text you entered and compare the whole header against it.
- If you want real HTTP Basic, enter
Basicfollowed by a base64username:passwordyourself, and still compare the whole value.
How Hookdeck Can Help: Hookdeck's DoorDash Drive source compares the whole Authorization value you paste, whatever its format. Hookdeck answers DoorDash with a 200 either way, but a request with a missing or wrong value is recorded as failed verification and never reaches your service.
Three attempts, then nothing
The Problem: A short outage or a bad deploy loses delivery updates, and your status pages stop at "picked up" for orders that were delivered.
Why It Happens: DoorDash sends each event at most three times. It does not document the interval between attempts, and there is no replay from the Portal.
Workarounds:
- Return
200 OKas soon as the header checks out and process afterwards. - Reconcile open deliveries against the Drive API, so a lost
DASHER_DROPPED_OFForDELIVERY_CANCELLEDis caught.
How Hookdeck Can Help: Hookdeck accepts each request and stores it, then delivers to your service with retries you configure and lets you replay anything that failed, so DoorDash's three attempts are spent against Hookdeck, not your service.
Only 200 OK counts
The Problem: A handler that returns 202 Accepted or 204 No Content receives every event three times.
Why It Happens: DoorDash retries any response other than 200 OK, not any non-2xx.
Workarounds:
- Return exactly
200from your webhook route. - Make handlers idempotent anyway, because duplicates can still happen.
How Hookdeck Can Help: Hookdeck answers DoorDash with a 200 as soon as it has the request, so your own response codes no longer decide whether DoorDash retries.
One endpoint per environment
The Problem: You cannot send delivery updates to two services, or split tracking events from status events, at the DoorDash end.
Why It Happens: Sandbox and Production each support a single webhook endpoint, and it receives every event.
Workarounds:
- Fan out from your own receiver.
- Keep the receiver thin, so one slow consumer does not delay the rest.
How Hookdeck Can Help: Point the one endpoint at a Hookdeck source, then use filters on event_name to send status events, tracking events and refunds to different destinations.
High-volume tracking events
The Problem: Turning on the dasher_enroute_* events multiplies traffic: one request every 30 seconds per active delivery.
Why It Happens: The tracking events stream the Dasher's location for the last 15 minutes before pickup and dropoff.
Workarounds:
- Handle tracking events on a separate, lightweight path that only updates a location cache.
- Size your endpoint for your peak number of concurrent deliveries.
How Hookdeck Can Help: Hookdeck can deliver tracking events to their own destination with a max delivery rate, so a spike in location updates never crowds out status changes.
Best practices
Key everything on external_delivery_id
Use the ID you supplied at creation to find the order. support_reference is the number to quote to DoorDash support, and tracking_url opens the delivery in the Merchant Portal.
Treat every field as optional
Fields are omitted until they are known, so check for presence before you read anything other than event_name and the identifiers. A missing dropoff_time_actual on DASHER_PICKED_UP is expected.
Deduplicate and order by updated_at
Retries mean the same event can arrive more than once, and nothing guarantees order. Build an idempotency key from external_delivery_id, event_name and created_at, and compare updated_at with what you stored before overwriting a status. See our guide to webhook idempotency.
Acknowledge fast and process asynchronously
Verify the header, store the payload, return 200, and send customer notifications from a background job. See why to process webhooks asynchronously.
Test the whole lifecycle in Sandbox
Run deliveries through the simulator, including cancellations and returns, before you request production access, so your handler has seen every event it will meet.
Conclusion
DoorDash Drive sends a flat delivery object with an event_name to one endpoint per environment, authenticated only by the text you entered as its Authorization header. Compare that whole value in constant time over HTTPS, return exactly 200 OK, key on external_delivery_id, treat every field as optional, and reconcile against the API so DoorDash's three attempts are not your only safety net.
Hookdeck Event Gateway checks the header, keeps every delivery update, and fans events out to as many services as you need, with retries and replay on your terms.
Get started with Hookdeck for free and handle DoorDash Drive webhooks reliably in minutes.