# Testing webhooks

> Try your webhook handler locally with signed sample events, then with real sandbox events.

Source: https://docs.delivr.no/webhooks/testing/

You can test your handler before Delivr can reach it: sign a sample event yourself, exactly as Delivr does, and post it to your local server.

## Send a signed event to localhost

This needs `openssl`, `jq` and `curl`. Use any secret while you test locally, and the real `whsec_…` once Delivr gives you one.

```bash
SECRET=whsec_local_test_secret
URL=http://localhost:3000/hooks/delivr

ID=$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid)
T=$(date +%s)
BODY=$(jq -cn --arg id "$ID" --arg at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '{
  id: $id, type: "order.picked_up", apiVersion: "2026-11-01", createdAt: $at,
  data: { orderId: "3f0e9a52-8a7e-4f8e-9a51-2c0f3c9d1b7a", reference: "DD-482913", externalRef: "order-1001",
          type: "on_demand", status: "picked_up", version: 9 }
}')
SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')

curl -i "$URL" \
  -H "Content-Type: application/json" \
  -H "User-Agent: Delivr-Webhooks/1" \
  -H "X-Delivr-Event: order.picked_up" \
  -H "X-Delivr-Event-Id: $ID" \
  -H "X-Delivr-Signature: t=$T,v1=$SIG" \
  -H "Delivr-Api-Version: 2026-11-01" \
  --data-binary "$BODY"
```

Then check what your handler does when you:

| Send                                                     | Your handler should                    |
| -------------------------------------------------------- | -------------------------------------- |
| The same request again (same `ID`)                       | Answer `2xx` and change nothing.       |
| A new `ID` with a lower `version`                        | Answer `2xx` and keep the newer state. |
| A `type` it does not know, such as `order.something_new` | Answer `2xx` and ignore it.            |
| A changed body with the old `SIG`                        | Reject it.                             |
| A `T` more than 5 minutes old                            | Reject it.                             |

Check your signature code against the [test vector](/webhooks/signatures/#test-vector) too.

## Receive real sandbox events

1. Expose your local server with a URL that does not change, such as an ngrok static domain or a Cloudflare named tunnel, and give it to Delivr (see [Get sandbox access](/sandbox/#get-sandbox-access)). To only look at events, a request inspector such as webhook.site works too.
2. Send [`POST /v1/merchant/webhooks/test`](/api/operations/sendwebhooktest/). Your endpoint gets `webhook.test`, with `data.test` set to `true`.
3. Run the [quickstart](/quickstart/). A robo driver delivers the order in about two minutes, and every status arrives as an event.
4. Read the [delivery log](/api/operations/listwebhookdeliveries/) to see what your endpoint answered, and [replay](/api/operations/replaywebhookevent/) any event to run it through your handler again.

```bash
curl -s "$DELIVR_API/v1/merchant/webhooks/deliveries?limit=10" \
  -H "Authorization: Bearer $DELIVR_API_KEY" | jq '.items[] | {event, attempt, statusCode, succeeded, error}'

curl -s -X POST "$DELIVR_API/v1/merchant/webhooks/events/$EVENT_ID/replay" \
  -H "Authorization: Bearer $DELIVR_API_KEY" \
  -H "Idempotency-Key: $(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid)"
```

To see retries, answer `500` for a while: the log shows each attempt on the [retry schedule](/webhooks/#retries).