> ## Documentation Index
> Fetch the complete documentation index at: https://docs-provider.voucher.allride.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Receive events at your endpoint.

## How webhooks work

When something happens that concerns you (a change request is decided, a set is activated, stock crosses a threshold, a voucher is redeemed), AllRide first **stores** the event, with its complete body and a stable `event.id`, for every webhook endpoint of yours that subscribes to that type; the delivery appears in your delivery log at once as `PENDING`. Then it sends an HTTPS `POST` with the stored body, signed at send time. The delivery ends as `DELIVERED` (your endpoint answered 2xx) or `FAILED` (the last of 5 attempts failed, or no request could be sent; `lastErrorCode` says why, see [Retries, logs and replay](https://docs-provider.voucher.allride.io/webhooks/retries-and-logs.md#why-a-delivery-failed-lasterrorcode)). The [event catalog](https://docs-provider.voucher.allride.io/webhooks/events.md) lists the provider types.

```mermaid theme={null}
sequenceDiagram
  participant A as AllRide
  participant R as Your receiver
  A->>A: store event (PENDING, stable event.id)
  A->>R: POST stored body, signed now (x-signing-signature)
  R->>R: verify signature, store event
  R-->>A: 2xx within 15 s
  R->>R: process asynchronously (deduplicate on event.id)
```

## Register an endpoint

Register endpoints in the developer dashboard under [Webhooks](https://provider-developers.voucher.allride.io/go/webhooks) or with the Workspace API (`manage-webhooks`, delegable to API keys):

```bash theme={null}
curl -sS -X POST "$VOUCHER_API_BASE_URL/partner/v1/webhook-endpoints" \
  -H "Authorization: Bearer $VOUCHER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "url": "'"$WEBHOOK_URL"'",
    "description": "Approvals and redemptions",
    "eventTypes": ["change-request-approved", "change-request-rejected", "redemption-created", "voucher-set-stock-threshold-reached"]
  }'
```

* The response contains `firstSecret` and `secondSecret`, **shown once**. Store both; see [Verifying signatures](https://docs-provider.voucher.allride.io/webhooks/verify-signatures.md).
* You can subscribe only to types your roles allow (`GET /partner/v1/webhook-event-types`); others → 400 `data-failed-validation` with `webhook-event-type-not-allowed`.
* Endpoints can be created only while API access is `active`. At most 10 endpoints with 30 types each.
* Endpoints can be updated, paused (`:pause`), resumed (`:resume`) and deleted. While paused nothing is sent; those events are logged as `endpoint-disabled` and can be replayed for 30 days.
* A change of URL or types is emailed to your owners and technical contact.

## Endpoint requirements

* `https` on port 443 or 8443, with a certificate from a public CA and TLS 1.2 or newer.
* Publicly resolvable. Private, loopback, link-local and cloud metadata addresses are refused when you save (422 `webhook-url-not-allowed`, with `more.reason`) and again before every delivery.
* Answer with any **2xx within 15 seconds**. Redirects are not followed and count as failures. Store the event and process it asynchronously.
* If you filter inbound traffic, allow AllRide's egress addresses ([Network and IP addresses](https://docs-provider.voucher.allride.io/guides/network.md#addresses-we-connect-from)).

## Payload format

Each delivery is a `POST` with `content-type: application/json`, `user-agent: AllRide-Voucher-Webhooks/1.0 (+https://docs-provider.voucher.allride.io/webhooks)` and `x-signing-signature`. The body, at most 64 KiB, has two members, `event` and `payload` (the format of AllRide's company webhooks):

```json theme={null}
{
  "event": {
    "id": "8d2e4b61-5a3f-4c7e-9b10-2f6a7c8d9e01",
    "type": "redemption-created",
    "timestamp": 1793700902
  },
  "payload": {
    "resource": "redemption",
    "id": 551203,
    "status": "completed",
    "voucherSetId": 4211,
    "voucherId": 7730512,
    "redeemedAt": "2026-11-03T10:15:02Z",
    "voidedAt": null,
    "_links": { "self": { "href": "https://api.voucher.allride.io/provider/v1/redemptions/551203", "type": "application/json" } }
  }
}
```

| Member | Meaning |
| - | - |
| `event.id` | Event id (a UUID), identical on every attempt and replay; deduplicate on it |
| `event.type` | Event type in lower-case kebab case, for example `redemption-created` |
| `event.timestamp` | When AllRide created the event, in Unix seconds (UTC); identical on every attempt. The signing time is `t` of `x-signing-signature` |
| `payload` | The resource the event is about, starting with `resource` and `id`, followed by its fields as the API returns them to you in a compact webhook view, including `_links` |

`payload` is a snapshot taken when the event was created. Re-fetch `_links.self` when you need the latest state. Provider payloads never contain consumer data: no consumer, redemption entity, link, channel, metadata or issued value. A partner that is also a consumer gets the consumer view of its own redemptions (documented on the consumer site) and the provider view of other consumers' redemptions of its sets.

## Delivery guarantees

* **At least once**: the same event can arrive more than once (after a failed or slow response). Make handlers idempotent by `event.id`.
* **Unordered, possibly late**: events can arrive in a different order than they happened. Events are stored before they are sent, so they are never silently dropped: if a send is delayed, an automatic sweep re-queues a delivery never attempted after about 10 minutes, or last attempted more than 30 minutes ago, so it may arrive late. A delivery that still cannot be sent after 3 re-queues becomes `FAILED` with `delivery-stalled` and can be replayed. Do not rely on order: use `event.timestamp` (or the resource's own timestamps such as `voidedAt`) to drop stale updates.
* **Stored, re-signed, retried**: the body is stored when the event occurs and signed at each send, so every attempt and replay has a fresh `t` in `x-signing-signature` while the body (and `event.id`) stays identical. A failed attempt is sent again with backoff, up to 5 attempts in total; a replay re-sends the same stored event with five new attempts, and the log keeps counting (`attemptNo`, `attemptCount`, `replayCount`). See [Retries, logs and replay](https://docs-provider.voucher.allride.io/webhooks/retries-and-logs.md).
* Deliveries are made only while your partner **and** its API access are `active`; the endpoint's `deliveriesBlockedReason` shows why nothing is sent otherwise. Events of such periods are not delivered later.
* The first attempt is sent within about 30 seconds of the event.

## Test events

Send a test event to an `active` endpoint, even one not subscribed to `webhook-endpoint-test`:

```bash theme={null}
curl -sS -X POST "$VOUCHER_API_BASE_URL/partner/v1/webhook-endpoints/11030:send-test-event" \
  -H "Authorization: Bearer $VOUCHER_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"message": "Hello from the sandbox"}'
```

The event (type `webhook-endpoint-test`, `payload = {resource: "webhook-endpoint", id, message}`) is stored first; the answer is 202 with its `webhookDeliveryId`, and the delivery is `PENDING` in the log until it is `DELIVERED` or `FAILED`. A paused or deleted endpoint → 409 `webhook-endpoint-not-active`. In the sandbox, active sets also get real `redemption-created` events from the sandbox consumer every few minutes.

## Related

* [Verifying signatures](https://docs-provider.voucher.allride.io/webhooks/verify-signatures.md)
* [Retries, logs and replay](https://docs-provider.voucher.allride.io/webhooks/retries-and-logs.md)
* [Event catalog](https://docs-provider.voucher.allride.io/webhooks/events.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.