Webhooks
Webhooks let a workspace receive HTTP callbacks when loyalty events occur (transactions, point changes, tier promotions, pass lifecycle, etc.). Each webhook has a signing secret, an event-type subscription, optional custom headers, and a delivery log with per-attempt detail and retry support.
Auth: Every endpoint is workspace-scoped — pass a Bearer token for a user who is a member of the workspace in the path (createWorkspaceGuard, source: "path").
All paths are mounted under the /wallethero-api prefix. Base URL: https://api.wallethero.app.
A workspace may have at most 25 active webhooks.
List Event Types
GET /wallethero-api/workspace/:workspaceId/webhooks/event-types
Returns the catalog of event types you can subscribe to plus the category names. Subscriptions accept any concrete event name, a category wildcard <category>.*, or the global wildcard *.
Auth: Bearer token — workspace member.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier |
Example Request
curl "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/webhooks/event-types" \
-H "Authorization: Bearer YOUR_TOKEN"Response (200)
{
"data": {
"event_types": [
"field_change.field_updated",
"transaction.purchase",
"transaction.refund",
"engagement.check_in",
"loyalty.points_earned",
"loyalty.tier_promoted",
"pass_lifecycle.pass_installed"
],
"categories": [
"field_change",
"transaction",
"engagement",
"loyalty",
"pass_lifecycle"
]
}
}The full event_types list also includes: field_change.field_increment, field_change.field_decrement, engagement.check_out, engagement.visit, engagement.scan, loyalty.points_spent, loyalty.points_expired, loyalty.points_adjusted, loyalty.tier_demoted, loyalty.reward_redeemed, loyalty.reward_cancelled, loyalty.reward_used, pass_lifecycle.pass_uninstalled, pass_lifecycle.pass_registered, pass_lifecycle.template_changed.
SDK
const { data } = await wh.webhooks.listEventTypes(workspaceId);List Webhooks
GET /wallethero-api/workspace/:workspaceId/webhooks
Returns all active (non-deleted) webhooks for the workspace, newest first. Secrets are never included in list responses.
Auth: Bearer token — workspace member.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier |
Example Request
curl "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/webhooks" \
-H "Authorization: Bearer YOUR_TOKEN"Response (200)
{
"data": [
{
"id": "9d1f8e2a-1234-4c5b-9a0e-abc123456789",
"workspace_id": "WORKSPACE_ID",
"name": "Orders sync",
"url": "https://example.com/hooks/wallethero",
"event_types": ["transaction.purchase", "loyalty.*"],
"enabled": true,
"description": null,
"headers": {},
"max_attempts": 5,
"date_created": "2026-06-01T10:00:00.000Z",
"date_updated": "2026-06-01T10:00:00.000Z"
}
]
}SDK
const { data } = await wh.webhooks.list(workspaceId);Create Webhook
POST /wallethero-api/workspace/:workspaceId/webhooks
Creates a webhook and generates its signing secret. The secret is returned once in this response (and again only on rotation) — store it securely. Responds 201.
Auth: Bearer token — workspace member.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | 1–120 chars |
url | string (URL) | Yes | Must be https://. http:// is only accepted when the server has WALLETHERO_ALLOW_INSECURE_WEBHOOK_URLS=true |
event_types | string[] | Yes | 1–50 entries. Each must be a known event name, a <category>.* wildcard, or * |
enabled | boolean | No | Defaults to true |
description | string | null | No | Up to 500 chars |
headers | Record<string, string> | No | Custom request headers (max 20) |
max_attempts | number | No | Integer 1–10. Defaults to 5 |
Example Request
curl -X POST "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/webhooks" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Orders sync",
"url": "https://example.com/hooks/wallethero",
"event_types": ["transaction.purchase", "loyalty.*"],
"max_attempts": 5
}'Response (201)
{
"data": {
"id": "9d1f8e2a-1234-4c5b-9a0e-abc123456789",
"workspace_id": "WORKSPACE_ID",
"name": "Orders sync",
"url": "https://example.com/hooks/wallethero",
"event_types": ["transaction.purchase", "loyalty.*"],
"enabled": true,
"description": null,
"headers": {},
"max_attempts": 5,
"secret": "f3a9...c21e",
"date_created": "2026-06-01T10:00:00.000Z",
"date_updated": "2026-06-01T10:00:00.000Z"
}
}SDK
const { data } = await wh.webhooks.create(workspaceId, {
name: "Orders sync",
url: "https://example.com/hooks/wallethero",
event_types: ["transaction.purchase", "loyalty.*"],
});Get Webhook
GET /wallethero-api/workspace/:workspaceId/webhooks/:id
Returns a single webhook (without its secret).
Auth: Bearer token — workspace member.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier |
id | string (UUID) | Webhook identifier |
Example Request
curl "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/webhooks/WEBHOOK_ID" \
-H "Authorization: Bearer YOUR_TOKEN"Response (200)
{
"data": {
"id": "WEBHOOK_ID",
"workspace_id": "WORKSPACE_ID",
"name": "Orders sync",
"url": "https://example.com/hooks/wallethero",
"event_types": ["transaction.purchase"],
"enabled": true,
"description": null,
"headers": {},
"max_attempts": 5,
"date_created": "2026-06-01T10:00:00.000Z",
"date_updated": "2026-06-01T10:00:00.000Z"
}
}SDK
const { data } = await wh.webhooks.get(workspaceId, webhookId);Update Webhook
PATCH /wallethero-api/workspace/:workspaceId/webhooks/:id
Partially updates a webhook. Only the fields you send are changed. The secret is not returned and cannot be set here (use rotate-secret).
Auth: Bearer token — workspace member.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier |
id | string (UUID) | Webhook identifier |
Request Body
All fields optional; same constraints as Create.
| Field | Type | Description |
|---|---|---|
name | string | 1–120 chars |
url | string (URL) | Must be https:// (see Create) |
event_types | string[] | 1–50 valid entries |
enabled | boolean | Enable/disable delivery |
description | string | null | Up to 500 chars |
headers | Record<string, string> | Max 20 |
max_attempts | number | Integer 1–10 |
Example Request
curl -X PATCH "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/webhooks/WEBHOOK_ID" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "enabled": false }'Response (200)
{
"data": {
"id": "WEBHOOK_ID",
"name": "Orders sync",
"enabled": false,
"event_types": ["transaction.purchase"],
"max_attempts": 5,
"date_updated": "2026-06-02T09:00:00.000Z"
}
}SDK
const { data } = await wh.webhooks.update(workspaceId, webhookId, {
enabled: false,
});Delete Webhook
DELETE /wallethero-api/workspace/:workspaceId/webhooks/:id
Soft-deletes the webhook (sets deleted_at). It stops receiving deliveries and disappears from list/get.
Auth: Bearer token — workspace member.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier |
id | string (UUID) | Webhook identifier |
Example Request
curl -X DELETE "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/webhooks/WEBHOOK_ID" \
-H "Authorization: Bearer YOUR_TOKEN"Response (200)
{ "data": { "success": true } }SDK
await wh.webhooks.delete(workspaceId, webhookId);Rotate Secret
POST /wallethero-api/workspace/:workspaceId/webhooks/:id/rotate-secret
Generates a new signing secret and returns it. The previous secret is immediately invalidated, so update your verification key.
Auth: Bearer token — workspace member.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier |
id | string (UUID) | Webhook identifier |
Example Request
curl -X POST "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/webhooks/WEBHOOK_ID/rotate-secret" \
-H "Authorization: Bearer YOUR_TOKEN"Response (200)
{
"data": {
"id": "WEBHOOK_ID",
"name": "Orders sync",
"secret": "a17b...9f02",
"event_types": ["transaction.purchase"],
"max_attempts": 5,
"date_updated": "2026-06-02T09:05:00.000Z"
}
}SDK
const { data } = await wh.webhooks.rotateSecret(workspaceId, webhookId);Test Webhook
POST /wallethero-api/workspace/:workspaceId/webhooks/:id/test
Sends a synthetic webhook.test event to the webhook's URL (signed with the current secret, including any custom headers) and returns the immediate result. A delivery row is recorded in a terminal state so the test shows up in the deliveries list. Rate-limited to one test per webhook every 10 seconds.
Auth: Bearer token — workspace member.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier |
id | string (UUID) | Webhook identifier |
Example Request
curl -X POST "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/webhooks/WEBHOOK_ID/test" \
-H "Authorization: Bearer YOUR_TOKEN"Response (200)
{
"data": {
"success": true,
"response_status": 200,
"response_body": "{\"ok\":true}",
"duration_ms": 142,
"error": null
}
}On failure success is false, response_status may be null (network/timeout), and error carries the reason (e.g. "HTTP 500").
SDK
const { data } = await wh.webhooks.test(workspaceId, webhookId);List Deliveries
GET /wallethero-api/workspace/:workspaceId/webhooks/:id/deliveries
Returns the delivery log for a single webhook, newest first, with paging and optional filters. Unlike most list endpoints, the response is { data, meta } at the top level (no extra wrapping).
Auth: Bearer token — workspace member.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier |
id | string (UUID) | Webhook identifier |
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
status | string | No | — | One of pending, processing, delivered, retrying, abandoned |
event_type | string | No | — | Filter by event type |
limit | number | No | 25 | Integer 1–200 |
offset | number | No | 0 | Integer ≥ 0 |
Example Request
curl "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/webhooks/WEBHOOK_ID/deliveries?status=abandoned&limit=50" \
-H "Authorization: Bearer YOUR_TOKEN"Response (200)
{
"data": [
{
"id": "d1e2...",
"workspace_id": "WORKSPACE_ID",
"webhook_id": "WEBHOOK_ID",
"event_type": "transaction.purchase",
"event_id": "evt_123",
"target_url": "https://example.com/hooks/wallethero",
"status": "delivered",
"attempt_count": 1,
"max_attempts": 5,
"scheduled_for": "2026-06-02T09:00:00.000Z",
"next_attempt_at": "2026-06-02T09:00:00.000Z",
"delivered_at": "2026-06-02T09:00:01.000Z",
"last_error": null,
"attempts": [
{
"attempted_at": "2026-06-02T09:00:01.000Z",
"status": "delivered",
"response_status": 200,
"duration_ms": 120,
"error": null
}
],
"date_created": "2026-06-02T09:00:00.000Z",
"date_updated": "2026-06-02T09:00:01.000Z"
}
],
"meta": { "total": 1, "limit": 50, "offset": 0 }
}SDK
const { data, meta } = await wh.webhooks.listDeliveries(
workspaceId,
webhookId,
{ status: "abandoned", limit: 50 },
);Get Delivery
GET /wallethero-api/workspace/:workspaceId/webhooks/deliveries/:deliveryId
Returns a single delivery with its full payload and per-attempt detail (request/response headers, bodies, durations, errors).
Auth: Bearer token — workspace member.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier |
deliveryId | string (UUID) | Delivery identifier |
Example Request
curl "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/webhooks/deliveries/DELIVERY_ID" \
-H "Authorization: Bearer YOUR_TOKEN"Response (200)
{
"data": {
"id": "DELIVERY_ID",
"webhook_id": "WEBHOOK_ID",
"event_type": "transaction.purchase",
"event_id": "evt_123",
"payload": { "...": "full event body" },
"target_url": "https://example.com/hooks/wallethero",
"target_headers": {},
"status": "abandoned",
"attempt_count": 5,
"max_attempts": 5,
"last_error": "HTTP 500",
"attempts": [
{
"attempted_at": "2026-06-02T09:00:01.000Z",
"status": "failed",
"request_headers": { "WalletHero-Event": "transaction.purchase" },
"response_status": 500,
"response_headers": {},
"response_body": "internal error",
"duration_ms": 230,
"error": "HTTP 500"
}
],
"date_created": "2026-06-02T09:00:00.000Z",
"date_updated": "2026-06-02T09:30:00.000Z"
}
}SDK
const { data } = await wh.webhooks.getDelivery(workspaceId, deliveryId);Retry Delivery
POST /wallethero-api/workspace/:workspaceId/webhooks/deliveries/:deliveryId/retry
Re-queues a delivery (e.g. one that was abandoned) for another attempt and returns the updated delivery record.
Auth: Bearer token — workspace member.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
workspaceId | string (UUID) | Workspace identifier |
deliveryId | string (UUID) | Delivery identifier |
Example Request
curl -X POST "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/webhooks/deliveries/DELIVERY_ID/retry" \
-H "Authorization: Bearer YOUR_TOKEN"Response (200)
{
"data": {
"id": "DELIVERY_ID",
"status": "pending",
"attempt_count": 5,
"max_attempts": 5,
"next_attempt_at": "2026-06-02T09:31:00.000Z"
}
}SDK
const { data } = await wh.webhooks.retryDelivery(workspaceId, deliveryId);