Skip to content

Notifications

Wallet notifications let you queue and deliver push messages to a client's Apple/Google Wallet pass, and let the client read their notification feed from the loyalty portal. Notifications are inserted in a pending state and delivered by a background cron (manual sends also drain the queue immediately).

This domain has two auth models:

  • Dashboard (admin) endpointsGET /notifications, GET /notifications/:id, POST /notifications. Bearer token of a user who is a member of the workspace (enforced by createWorkspaceGuard). The workspace is resolved from workspace_id in the query (list), from the loaded notification row (get), or from workspace_id in the body (send).
  • Public loyalty portal endpointsGET /public/loyalty/notifications, GET /public/loyalty/notifications/unread-count, PATCH /public/loyalty/notifications/:id/read. Authenticated with a client token issued by the loyalty portal session, passed either as a token query parameter or an x-client-token header. The client and workspace are taken from the token, never from request input.

List notifications (admin)

GET /wallethero-api/notifications

Lists notifications for a workspace in any status, newest first. Supports filtering by client, status, channel, source type, and date range.

Auth: Bearer token — workspace member.

Query Parameters

ParameterTypeRequiredDefaultDescription
workspace_idstring (UUID)YesWorkspace to list notifications for (also used for the access check).
client_idstring (UUID)NoRestrict to a single client.
statusstringNoOne of pending, delivered, received, read, archived, failed.
channelstringNoCurrently only wallet_notification.
source_typestringNoOne of earning_rule, campaign, manual, reward_redemption, tier.
from_datestring (ISO 8601)NoOnly notifications created on/after this timestamp.
to_datestring (ISO 8601)NoOnly notifications created on/before this timestamp.
limitnumberNo50Page size, 11000.
offsetnumberNo0Pagination offset.

Example Request

bash
curl "https://api.wallethero.app/wallethero-api/notifications?workspace_id=WORKSPACE_ID&status=delivered&limit=20" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response (200)

json
{
  "data": [
    {
      "id": "8f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
      "date_created": "2026-06-16T09:30:00.000Z",
      "workspace_id": "WORKSPACE_ID",
      "client_id": "CLIENT_ID",
      "channel": "wallet_notification",
      "title": "Points added",
      "body": "You earned 50 points!",
      "metadata": {},
      "source_type": "manual",
      "source_id": null,
      "status": "delivered",
      "delivered_at": "2026-06-16T09:30:05.000Z",
      "read_at": null,
      "scheduled_for": "2026-06-16T09:30:00.000Z",
      "pass_id": "PASS_ID"
    }
  ],
  "total": 1
}

SDK

typescript
const result = await wh.notifications.list({
  workspace_id: workspaceId,
  status: "delivered",
  limit: 20,
});
// result.data: Notification[], result.total: number

Get a notification (admin)

GET /wallethero-api/notifications/:id

Returns a single notification by ID, with pass/client joins hydrated by the service. Workspace membership is enforced by pre-loading the notification row and checking the caller against its workspace_id.

Auth: Bearer token — workspace member (membership derived from the notification's workspace).

Path Parameters

ParameterTypeDescription
idstring (UUID)Notification identifier.

Example Request

bash
curl "https://api.wallethero.app/wallethero-api/notifications/NOTIFICATION_ID" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response (200)

json
{
  "data": {
    "id": "NOTIFICATION_ID",
    "workspace_id": "WORKSPACE_ID",
    "client_id": "CLIENT_ID",
    "channel": "wallet_notification",
    "title": "Points added",
    "body": "You earned 50 points!",
    "metadata": {},
    "status": "read",
    "read_at": "2026-06-16T10:00:00.000Z"
  }
}

Returns 404 with { "error": "Notification not found" } if the ID does not exist.

SDK

typescript
const { data } = await wh.notifications.get(notificationId);

Send a notification (admin)

POST /wallethero-api/notifications

Queues a manual wallet notification to a client. The notification is inserted as pending; the delivery queue is drained immediately after insert (fire-and-forget) so manual sends do not wait for the next cron tick. For wallet_notification the client must have at least one active pass, otherwise the request fails with 422.

Auth: Bearer token — workspace member (membership derived from workspace_id in the body).

Request Body

FieldTypeRequiredDescription
workspace_idstring (UUID)YesWorkspace the notification belongs to.
client_idstring (UUID)YesRecipient client.
channelstringNoDelivery channel; defaults to wallet_notification (currently the only value).
titlestringNoNotification title (max 255 chars).
bodystringYesNotification body (min 1 char).
metadataobjectNoArbitrary key/value metadata.
source_typestringNoOne of earning_rule, campaign, manual, reward_redemption, tier. Defaults to manual.
source_idstringNoOptional reference to the source object (max 255 chars).
expires_atstring (ISO 8601)NoWhen the notification expires.
pass_idstring (UUID)NoTarget a specific pass.
scheduled_forstring (ISO 8601)NoSchedule delivery for a future time (otherwise computed from rate limits).

Example Request

bash
curl -X POST "https://api.wallethero.app/wallethero-api/notifications" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "workspace_id": "WORKSPACE_ID",
    "client_id": "CLIENT_ID",
    "title": "Points added",
    "body": "You earned 50 points!"
  }'

Response (201)

json
{ "data": { "notification_id": "8f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f" } }

If the client has no active passes:

json
{ "error": "Client has no active passes", "code": "NO_ACTIVE_PASSES" }

(HTTP 422.)

SDK

typescript
const { data } = await wh.notifications.send({
  workspace_id: workspaceId,
  client_id: clientId,
  title: "Points added",
  body: "You earned 50 points!",
});
// data.notification_id

List client notifications (loyalty portal)

GET /wallethero-api/public/loyalty/notifications

Returns the signed-in client's own notification feed. Only notifications that have left the queue are returned (status delivered, received, read, or archived); pending and failed are never exposed. The client and workspace come from the client token.

Auth: Client token (loyalty portal session) — pass as ?token=... or x-client-token header.

Query Parameters

ParameterTypeRequiredDefaultDescription
tokenstringYes*Client session token (or send it as the x-client-token header instead).
channelstringNoFilter by channel (wallet_notification).
source_typestringNoOne of earning_rule, campaign, manual, reward_redemption, tier.
from_datestring (ISO 8601)NoOnly notifications created on/after this timestamp.
to_datestring (ISO 8601)NoOnly notifications created on/before this timestamp.
limitnumberNo50Page size, 11000.
offsetnumberNo0Pagination offset.

* The token is required either as the token query parameter or the x-client-token header.

Example Request

bash
curl "https://api.wallethero.app/wallethero-api/public/loyalty/notifications?limit=20" \
  -H "x-client-token: CLIENT_TOKEN"

Response (200)

json
{
  "data": [
    {
      "id": "NOTIFICATION_ID",
      "channel": "wallet_notification",
      "title": "Points added",
      "body": "You earned 50 points!",
      "status": "delivered",
      "delivered_at": "2026-06-16T09:30:05.000Z",
      "read_at": null
    }
  ],
  "total": 1
}

SDK

No SDK method — call the REST endpoint directly with the client session token.


Unread count (loyalty portal)

GET /wallethero-api/public/loyalty/notifications/unread-count

Returns the number of unread notifications (status delivered or received) for the signed-in client.

Auth: Client token (loyalty portal session) — pass as ?token=... or x-client-token header.

Example Request

bash
curl "https://api.wallethero.app/wallethero-api/public/loyalty/notifications/unread-count" \
  -H "x-client-token: CLIENT_TOKEN"

Response (200)

json
{ "data": { "count": 3 } }

SDK

No SDK method for the portal endpoint — call the REST endpoint directly with the client session token.

Note: the SDK's wh.notifications.getUnreadCount(workspaceId, clientId) is an admin helper that queries GET /wallethero-api/notifications?...&status=delivered&limit=0; it is not the portal endpoint above.


Mark notification as read (loyalty portal)

PATCH /wallethero-api/public/loyalty/notifications/:id/read

Marks one of the client's own notifications as read. Only notifications currently in delivered or received state can transition to read; the update is scoped to the token's client.

Auth: Client token (loyalty portal session) — pass as ?token=... or x-client-token header.

Path Parameters

ParameterTypeDescription
idstring (UUID)Notification identifier.

Example Request

bash
curl -X PATCH "https://api.wallethero.app/wallethero-api/public/loyalty/notifications/NOTIFICATION_ID/read" \
  -H "x-client-token: CLIENT_TOKEN"

Response (200)

json
{ "data": { "success": true } }

If the notification does not exist or is not in a delivered/received state:

json
{ "error": "Notification not found or not in a delivered state" }

(HTTP 404.)

SDK

No SDK method — call the REST endpoint directly with the client session token.

WalletHero Documentation