# Guide to Uber Webhooks: Features and Best Practices

Uber Eats webhooks notify your systems when order activity happens on a connected store: a new order comes in, a scheduled order is placed, an order is canceled. If you're building an order-management or POS integration on Uber Eats, webhooks are how you react in real time without polling.

This guide covers how Uber Eats webhooks work, the events you'll handle, how to verify the `X-Uber-Signature`, the retry behavior, and the best practices for production, plus a note on how Uber Direct's signing differs.

## What are Uber webhooks?

Uber Eats webhooks are HTTP POSTs delivered to a Primary Webhook URL you configure, each carrying a JSON event about an order or store. Each delivery is signed with an `X-Uber-Signature` header you verify with your app's client secret.

One thing to be clear about up front: this guide covers Uber Eats. Uber Direct (Deliveries) uses a different signing key (a per-webhook Signing Key rather than the client secret), covered briefly at the end.

## Uber Eats webhook features

| Feature | Details |
| --- | --- |
| Configuration | Developer Dashboard (Webhooks tab), one Primary Webhook URL |
| Signature header | `X-Uber-Signature` |
| Signature scheme | lowercase hex HMAC-SHA256 over the raw body, keyed with the client secret |
| Acknowledgement | HTTP 200 with an empty body |
| Retries | On 500/502/503/504, timeouts, network errors; backoff 10s, 30s, 60s, 120s, then exponential (~7 attempts) |
| Auth options | Basic Auth or OAuth on the endpoint |
| SDK | No official webhook SDK |
| Uber Direct | Separate: per-webhook Signing Key, `x-uber-signature` / `x-postmates-signature` |

## Common events

Uber Eats events cover the order lifecycle and store provisioning:

| Event | Fires when |
| --- | --- |
| `orders.notification` | A new order is received |
| `orders.scheduled.notification` | A scheduled order is placed |
| `orders.cancel` | An order is canceled |
| `store.provisioned` | A store is provisioned to your integration |

Subscribe to the events you process, and consult Uber's webhook reference for the full list.

## Setting up Uber webhooks

Configure a single Primary Webhook URL per integration in the Developer Dashboard's Webhooks tab, with Basic Auth or OAuth on the endpoint. Respond to every delivery with HTTP 200 and an empty body to acknowledge.

## Securing Uber webhooks

Every Uber Eats delivery carries an `X-Uber-Signature` header: a lowercase hex HMAC-SHA256 of the raw request body, keyed with your app's client secret. Compute over the exact raw bytes and compare in constant time.

```javascript
const crypto = require("crypto");

const CLIENT_SECRET = process.env.UBER_CLIENT_SECRET;

function verify(rawBody, signature) {
  const expected = crypto
    .createHmac("sha256", CLIENT_SECRET)
    .update(rawBody)
    .digest("hex"); // lowercase hex
  const a = Buffer.from(expected);
  const b = Buffer.from((signature || "").toLowerCase());
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  if (!verify(req.body, req.headers["x-uber-signature"])) {
    return res.sendStatus(401);
  }

  res.status(200).send(""); // empty-body 200
  processQueue.add(JSON.parse(req.body)); // process asynchronously
});

```

The same check in Python:

```python
import hashlib
import hmac
import os

CLIENT_SECRET = os.environ["UBER_CLIENT_SECRET"].encode()

def verify(raw_body: bytes, signature: str) -> bool:
    expected = hmac.new(CLIENT_SECRET, raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, (signature or "").lower())

```

### How Uber Direct differs

Uber Direct (Deliveries) webhooks are signed with a dedicated per-webhook Signing Key (not your client secret) and use `x-uber-signature` / `x-postmates-signature`. If your integration spans both products, key each verifier to the correct secret; a single client-secret check won't validate Direct deliveries.

## Uber webhook limitations and pain points

### Two products, two signing keys

The Problem: Uber Eats signs with the client secret; Uber Direct signs with a per-webhook Signing Key. Code that assumes one key rejects the other product's deliveries.

Why It Happens: Eats and Direct are separate products with separate signing models under a shared "Uber" umbrella.

Workarounds:

* Route by product and verify Eats with the client secret, Direct with its per-webhook Signing Key.

How Hookdeck Can Help: Hookdeck verifies each source with the right secret at the edge, so your application receives pre-verified events regardless of which Uber product sent them.

### Empty-body 200 required

The Problem: Uber expects an HTTP 200 with an empty body. Returning a JSON body or a non-200 is treated as a failed delivery and triggers retries.

Why It Happens: Uber's ack contract is a bare 200.

Workarounds:

* Send `200` with an empty body and defer processing to a queue.

How Hookdeck Can Help: Hookdeck acknowledges Uber correctly and durably queues the event, so your processing time never affects the ack.

### Retries bring duplicates

The Problem: Uber retries on 5xx, timeouts, and network errors with backoff up to ~7 attempts, so the same event can arrive more than once.

Why It Happens: At-least-once delivery favors eventual delivery of order events.

Workarounds:

* Dedupe on an order/event identifier and make side effects idempotent.

How Hookdeck Can Help: Hookdeck deduplicates deliveries at the edge, so retries don't double-process orders. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### No official webhook SDK

The Problem: There's no official SDK helper for verifying Uber webhooks, so you implement the HMAC yourself.

Why It Happens: Uber's SDKs target the APIs, not webhook ingestion.

Workarounds:

* Use the constant-time HMAC verification above, keyed correctly per product.

How Hookdeck Can Help: Hookdeck verifies the signature at the edge, so your app receives pre-verified events without hand-rolled crypto.

## Best practices

### Verify with the correct key per product

Eats: lowercase hex HMAC-SHA256 over the raw body keyed with the client secret. Direct: the per-webhook Signing Key. Compare in constant time.

### Acknowledge with an empty 200, process asynchronously

Return an empty-bodied 200 immediately and defer work to a queue. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

### Dedupe on the order/event ID

Use an order or event identifier as an idempotency key so retries don't double-process.

### Secure the endpoint

Layer Basic Auth or OAuth on the endpoint in addition to signature verification for defense in depth.

## Conclusion

Uber Eats webhooks put order and store activity on a push channel your integration can act on, verified with an `X-Uber-Signature` lowercase-hex HMAC keyed with your client secret, acknowledged with an empty-bodied 200. The main traps are the empty-200 contract, at-least-once retries that bring duplicates, and the fact that Uber Direct signs with a different key entirely.

[Hookdeck](https://hookdeck.com) verifies the signature (per product), deduplicates, and durably queues every event at the edge, so your app only ever processes verified, unique order events.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Uber webhooks reliably in minutes.