Skip to content

Other & Distribution-adjacent Endpoints

This page is a catch-all reference covering the remaining wallethero-api endpoints: workspace settings, SSO login, image-source helpers, engagement tracking, the embeddable distribution widget, distribution API tokens, template migration, internal passserver endpoints, and the test-only e2e seams.

Auth varies per endpoint — most are workspace-scoped Bearer-token calls (caller must be a member of the workspace), a few are public/token-authed (SSO, the embed widget), and the internal pass endpoints require the static admin token. Each endpoint states its own auth model below.

All paths include the /wallethero-api prefix. Base URL is https://api.wallethero.app.


Workspace Settings

Per-workspace defaults (currency, locale). The settings row is created on first read if it does not yet exist (defaults: USD / en).


Get workspace settings

GET /wallethero-api/workspace/:workspaceId/settings

Returns the workspace settings, creating the row with defaults if absent.

Auth: Bearer token — workspace member. Also accepts a mobile-app session via the x-mobile-token header (read-only).

Path Parameters

ParameterTypeDescription
workspaceIdstring (UUID)Workspace identifier

Example Request

bash
curl "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/settings" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response (200)

json
{
  "data": {
    "id": "11111111-1111-1111-1111-111111111111",
    "workspace_id": "WORKSPACE_ID",
    "default_currency": "USD",
    "default_locale": "en",
    "date_created": "2026-01-01T00:00:00.000Z",
    "date_updated": "2026-01-01T00:00:00.000Z"
  }
}

SDK

typescript
const settings = await wh.workspaceSettings.getSettings(workspaceId);

Update workspace settings

PATCH /wallethero-api/workspace/:workspaceId/settings

Updates the workspace defaults. The row is created first if it does not exist.

Auth: Bearer token — workspace member.

Path Parameters

ParameterTypeDescription
workspaceIdstring (UUID)Workspace identifier

Request Body

FieldTypeRequiredDescription
default_currencystringNoISO 4217 currency code, exactly 3 letters, uppercased server-side (e.g. EUR)
default_localestringNoLocale code, 2–10 chars (e.g. pl)

Example Request

bash
curl -X PATCH "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/settings" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "default_currency": "EUR", "default_locale": "pl" }'

Response (200)

json
{
  "data": {
    "id": "11111111-1111-1111-1111-111111111111",
    "workspace_id": "WORKSPACE_ID",
    "default_currency": "EUR",
    "default_locale": "pl",
    "date_created": "2026-01-01T00:00:00.000Z",
    "date_updated": "2026-06-16T10:00:00.000Z"
  }
}

SDK

typescript
const settings = await wh.workspaceSettings.updateSettings(workspaceId, {
  default_currency: "EUR",
  default_locale: "pl",
});

SSO

Single-sign-on login. Exchanges a third-party identity provider token for a Directus session. Runs before any workspace context exists.


SSO login

POST /wallethero-api/auth/sso

Verifies the provider token, finds or creates the matching Directus user, and issues a Directus session.

Auth: Public — no Bearer token. The request body carries the provider token.

Request Body

FieldTypeRequiredDescription
providerstring enumYesIdentity provider. Currently only google.
tokenstringYesThe provider-issued token to verify (e.g. a Google ID token).

Example Request

bash
curl -X POST "https://api.wallethero.app/wallethero-api/auth/sso" \
  -H "Content-Type: application/json" \
  -d '{ "provider": "google", "token": "GOOGLE_ID_TOKEN" }'

Response (200)

json
{
  "access_token": "eyJhbGciOi...",
  "refresh_token": "abc123...",
  "user": {
    "id": "22222222-2222-2222-2222-222222222222",
    "email": "[email protected]",
    "first_name": "Jane",
    "last_name": "Doe"
  }
}

Note: this response is not wrapped in { "data": ... }.

SDK

No SDK method — call the REST endpoint directly.


Image Source

Classify and validate pass-template image fields, and invalidate cached external image URLs. Used by the template editor to determine whether a stored value is a Directus file UUID, an https:// URL, or a ${custom_fields.x} reference.


Preview an image source value

POST /wallethero-api/workspace/:workspaceId/image-source/preview

Classifies and lightly validates a single image-source value. Does not download external URLs.

Auth: Bearer token — workspace member.

Path Parameters

ParameterTypeDescription
workspaceIdstring (UUID)Workspace identifier

Request Body

FieldTypeRequiredDescription
rawstringYesThe stored image-source value to classify (file UUID, https:// URL, or ${custom_fields.<name>})

Example Request

bash
curl -X POST "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/image-source/preview" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "raw": "https://cdn.example.com/logo.png" }'

Response (200)

json
{
  "data": {
    "kind": "url",
    "raw": "https://cdn.example.com/logo.png",
    "url": "https://cdn.example.com/logo.png",
    "previewUrl": "https://cdn.example.com/logo.png",
    "cachedFileId": null,
    "valid": true
  }
}

kind is one of uuid, url, custom_field, empty. When valid is false a reason string explains why.

SDK

typescript
const result = await wh.imageSource.preview(workspaceId, "https://cdn.example.com/logo.png");

Refresh (invalidate) a cached image URL

POST /wallethero-api/workspace/:workspaceId/image-source/refresh

Drops the cached external URL → Directus file mapping for the given URL so the next pass generation re-downloads it. The lookup is scoped to the caller's workspace. No-op if no cache entry exists.

Auth: Bearer token — workspace member.

Path Parameters

ParameterTypeDescription
workspaceIdstring (UUID)Workspace identifier

Request Body

FieldTypeRequiredDescription
sourceUrlstringYesThe external http:///https:// URL whose cache entry should be invalidated

Example Request

bash
curl -X POST "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/image-source/refresh" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "sourceUrl": "https://cdn.example.com/logo.png" }'

Response (200)

json
{ "success": true }

SDK

typescript
await wh.imageSource.refresh(workspaceId, "https://cdn.example.com/logo.png");

Engagement Tracking

Record customer engagement events (check-in, check-out, visit, scan). The event is persisted to client_events and dispatched to webhooks via the event bus.


Record an engagement event

POST /wallethero-api/workspace/:workspaceId/engagement

Persists an engagement event for a client (optionally tied to a pass) and returns the stored row.

Auth: Bearer token — workspace member.

Path Parameters

ParameterTypeDescription
workspaceIdstring (UUID)Workspace identifier

Request Body

FieldTypeRequiredDescription
client_idstring (UUID)YesClient the event belongs to. Must exist in this workspace.
event_typestring enumYesOne of check_in, check_out, visit, scan.
pass_idstring (UUID)NoPass associated with the event. If given, must exist in this workspace.
event_timestampstring (ISO 8601)NoEvent time. Defaults to the server's current time.
sourcestringNoFree-text source label. Defaults to api.
metadataobjectNoArbitrary key/value metadata. Defaults to {}.

Example Request

bash
curl -X POST "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/engagement" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "33333333-3333-3333-3333-333333333333",
    "event_type": "check_in",
    "source": "front-desk",
    "metadata": { "location": "store-1" }
  }'

Response (201)

json
{
  "data": {
    "id": "44444444-4444-4444-4444-444444444444",
    "workspace_id": "WORKSPACE_ID",
    "client_id": "33333333-3333-3333-3333-333333333333",
    "event_category": "engagement",
    "event_type": "check_in",
    "event_timestamp": "2026-06-16T10:00:00.000Z",
    "pass_id": null,
    "source": "front-desk",
    "metadata": { "location": "store-1" }
  }
}

SDK

No SDK method — call the REST endpoint directly. (The SDK events service records engagement via the generic events API, not this endpoint.)


Embeddable Distribution Widget

Public endpoints reached from third-party origins (CORS-enabled). Authorized by the per-distribution token in the URL path — not a Directus user. Used by the embeddable enrollment widget / "Plain HTML or SDK snippet" from the distribution editor.


Get distribution embed config

GET /wallethero-api/embed/distribution/:token/config

Returns the public configuration the widget needs to render its form: status, delivery mode, form-field config, and basic workspace info.

Auth: Public — the :token path segment is the per-distribution API token (validated by the distribution-token middleware). CORS is applied for the distribution's allowed origins.

Path Parameters

ParameterTypeDescription
tokenstringThe per-distribution API token.

Example Request

bash
curl "https://api.wallethero.app/wallethero-api/embed/distribution/DISTRIBUTION_TOKEN/config"

Response (200)

json
{
  "data": {
    "id": "55555555-5555-5555-5555-555555555555",
    "status": "active",
    "delivery_mode": "email",
    "form_fields_config": null,
    "workspace": {
      "id": "WORKSPACE_ID",
      "name": "Acme Loyalty",
      "slug": "acme"
    }
  },
  "success": true
}

SDK

typescript
// Browser widget helper (PublicDistributionService):
const config = await publicDistribution.getConfig();

Submit distribution enrollment

POST /wallethero-api/embed/distribution/:token/submit

Enrolls a submitter into the distribution: creates/locates the client, issues a pass, and (per the distribution's delivery mode) emails it and/or returns inline pass URLs.

Auth: Public — the :token path segment is the per-distribution API token. CORS is applied for the distribution's allowed origins.

A bare GET on this URL returns 405 Method Not Allowed with a hint to use POST.

Path Parameters

ParameterTypeDescription
tokenstringThe per-distribution API token.

Request Body

FieldTypeRequiredDescription
emailstring (email)YesSubmitter's email address.
first_namestringYesSubmitter's first name (min length 1).
last_namestringNoSubmitter's last name.
phonestringNo*Submitter's phone number (trimmed, max 50 characters). Top-level field, not a custom field — it is what the server checks when form_fields_config marks phone required, and what it matches returning clients on. A phone sent only as custom_fields.phone is still accepted and promoted.
marketing_consentbooleanNo*Whether the submitter consented to marketing. Must be true when form_fields_config marks the consent checkbox required.
custom_fieldsobjectNoArbitrary key/value custom-field values for the client.
referral_codestringNoReferral code (trimmed, max 20 chars).

Example Request

bash
curl -X POST "https://api.wallethero.app/wallethero-api/embed/distribution/DISTRIBUTION_TOKEN/submit" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "[email protected]",
    "first_name": "Jane",
    "last_name": "Doe",
    "phone": "+15551234567",
    "marketing_consent": true
  }'

Response (200)

json
{
  "data": {
    "pass_id": "66666666-6666-6666-6666-666666666666",
    "apple_pass_url": "https://api.wallethero.app/...",
    "google_pass_url": "https://pay.google.com/...",
    "client_token": "ct_abc123"
  },
  "isResend": false,
  "emailSent": true,
  "message": "Pass issued successfully.",
  "success": true
}

When a pass already exists for the email, isResend is true and the existing pass is returned.

SDK

typescript
// Browser widget helper (PublicDistributionService):
const result = await publicDistribution.enroll({
  email: "[email protected]",
  first_name: "Jane",
  last_name: "Doe",
  marketing_consent: true,
});

Submit URL — GET guard

GET /wallethero-api/embed/distribution/:token/submit

A convenience guard so that opening the submit URL in a browser (which issues a GET) returns a friendly error instead of a bare Directus 404. The submission endpoint itself is POST only (see above).

Auth: Public — no token validation is performed on this GET; it always responds with 405.

Response (405)

json
{
  "success": false,
  "error": "This endpoint accepts POST only. Use the Plain HTML or SDK snippet from the distribution editor, or curl with -X POST and a JSON body."
}

The response also sets the Allow: POST, OPTIONS header.

No SDK method — this is a browser-safety guard, not a callable endpoint.


Distribution API Tokens

Manage the per-distribution API token used by the embeddable widget.


Rotate a distribution's API token

POST /wallethero-api/workspace/:workspaceId/distributions/:distributionId/api-token/rotate

Generates a new API token for the distribution and returns the plaintext value exactly once — it is stored hashed and cannot be retrieved later. Verifies the distribution belongs to the workspace.

Auth: Bearer token — workspace member.

Path Parameters

ParameterTypeDescription
workspaceIdstring (UUID)Workspace identifier
distributionIdstring (UUID)Distribution identifier

Example Request

bash
curl -X POST "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/distributions/DISTRIBUTION_ID/api-token/rotate" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response (200)

json
{
  "data": {
    "distribution_id": "DISTRIBUTION_ID",
    "token": "whd_live_abc123...",
    "prefix": "whd_live"
  },
  "message": "API token rotated. Save the plaintext token now — it cannot be retrieved later."
}

SDK

typescript
const { data } = await wh.distributions.rotateApiToken(workspaceId, distributionId);
// data.token is the plaintext token — store it now.

Templates

Pass-template lifecycle helper. (Basic CRUD for pass_templates lives under /wallethero-api/pass-templates/...; the migration action below is documented here.)


Migrate passes and delete a template

POST /wallethero-api/templates/:templateId/migrate-and-delete

Moves all passes from the source template to target_template_id (also updating their project_id), then deletes the now-empty source template. Both templates must belong to the same workspace.

Auth: Bearer token — workspace member. The guard loads the source template and enforces membership against its workspace_id.

Path Parameters

ParameterTypeDescription
templateIdstring (UUID)Source template to migrate from and delete.

Request Body

FieldTypeRequiredDescription
target_template_idstring (UUID)YesTemplate to move passes to. Must differ from the source and belong to the same workspace.

Example Request

bash
curl -X POST "https://api.wallethero.app/wallethero-api/templates/SOURCE_TEMPLATE_ID/migrate-and-delete" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "target_template_id": "TARGET_TEMPLATE_ID" }'

Response (200)

json
{
  "data": {
    "deleted_template_id": "SOURCE_TEMPLATE_ID",
    "target_template_id": "TARGET_TEMPLATE_ID",
    "migrated_passes": 42
  }
}

Returns 404 if the target template is not found, and 403 if the target belongs to a different workspace.

SDK

typescript
const { data } = await wh.passTemplates.migrateAndDelete(sourceTemplateId, targetTemplateId);

Internal Pass Endpoints

Read-mostly endpoints consumed by the passserver on every wallet refresh. The /internal/* variants are gated to admins because the loyalty snapshot includes the client's portal_url (a long-lived capability link). A workspace-scoped sibling exists for the editor's pass preview.


Mark a pass as fetched

POST /wallethero-api/internal/passes/:passId/fetched

Marks queued notifications for the pass as received after a device (Apple) fetch or a successful Google Wallet object update. Idempotent and best-effort — never errors when the pass is unknown.

Auth: Admin only (static admin DIRECTUS_TOKEN). Consumed by the passserver.

Path Parameters

ParameterTypeDescription
passIdstring (UUID)Pass identifier

Request Body

FieldTypeRequiredDescription
sourcestring enumNoapple_fetch or google_update.
fetched_atstring (ISO 8601)NoFetch timestamp.

Example Request

bash
curl -X POST "https://api.wallethero.app/wallethero-api/internal/passes/PASS_ID/fetched" \
  -H "Authorization: Bearer ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "source": "apple_fetch" }'

Response (200)

json
{ "data": { "updated": 1 } }

SDK

No SDK method — call the REST endpoint directly.


Get loyalty render data (internal)

GET /wallethero-api/internal/passes/:passId/loyalty-render-data

Returns the live loyalty snapshot (points, tier, portal URL, tier benefits, active/available rewards, referral code) used to render the pass. Lazily provisions a referral code if the referral program is enabled.

Auth: Admin only (static admin DIRECTUS_TOKEN). Consumed by the passserver.

Path Parameters

ParameterTypeDescription
passIdstring (UUID)Pass identifier

Example Request

bash
curl "https://api.wallethero.app/wallethero-api/internal/passes/PASS_ID/loyalty-render-data" \
  -H "Authorization: Bearer ADMIN_TOKEN"

Response (200)

json
{
  "data": {
    "points": 1200,
    "tier": { "name": "Gold" },
    "portal_url": "https://portal.wallethero.app/...",
    "referral_code": "JANE-XYZ"
  }
}

Returns 404 { "errors": [{ "message": "Pass not found" }] } for an unknown pass, and an empty snapshot when the pass has no associated client.

SDK

No SDK method — call the REST endpoint directly (internal/admin only).


Get loyalty render data (workspace-scoped)

GET /wallethero-api/workspace/:workspaceId/passes/:passId/loyalty-render-data

Workspace-scoped sibling of the internal endpoint, used by the editor's pass preview so ${wallet.points} (and tier/etc) resolve to the same values the passserver renders. The pass must belong to the given workspace.

Auth: Bearer token — workspace member.

Path Parameters

ParameterTypeDescription
workspaceIdstring (UUID)Workspace identifier
passIdstring (UUID)Pass identifier

Example Request

bash
curl "https://api.wallethero.app/wallethero-api/workspace/WORKSPACE_ID/passes/PASS_ID/loyalty-render-data" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response (200)

json
{
  "data": {
    "points": 1200,
    "tier": { "name": "Gold" },
    "portal_url": "https://portal.wallethero.app/...",
    "referral_code": "JANE-XYZ"
  }
}

SDK

typescript
const { data } = await wh.passes.getLoyaltyRenderData(workspaceId, passId);

Test-only endpoints (non-production)

These endpoints are NOT part of the public contract. They are mounted only when the WALLETHERO_E2E_TEST_HOOKS=true environment flag is set and are never enabled in production. They exist so the e2e suite can drive internal seams (simulate inbound IDPoS transactions, read back outbound IDPoS calls, trigger crons, seed mappings) without standing up the surrounding infrastructure. They do not use the workspace guard — they accept whatever the e2e harness sends as an authenticated admin. Listed for completeness only.

Method & PathPurpose
POST /wallethero-api/__test__/idpos/ingestSimulate an inbound IDPoS transaction: resolve the client via customer_id_mapping, dedupe by external_id, then create a transaction (fires the production hooks → automations → tier pipeline). Body: workspace_id, external_id, amount (required); idpos_customer_id / customer_id, currency, description (optional).
GET /wallethero-api/__test__/idpos/callsRead back the outbound calls the IdposClient would have made (recorded in-memory).
POST /wallethero-api/__test__/idpos/calls/clearClear the recorded outbound IDPoS-call buffer.
POST /wallethero-api/__test__/automations/run-time-basedTrigger the time-based automations cron path on demand (publishes test.run_time_based_automations on the domain event bus).
POST /wallethero-api/__test__/passes/simulate-apple-installCreate an apple_registrations row so the registration hook fires a pass_lifecycle.pass_installed event. Body: workspace_id, pass_id (required); project_id, device_library_identifier, pass_type_identifier, serial_number, push_token (optional).
POST /wallethero-api/__test__/notifications/run-deliveryFlush the notification delivery queue synchronously instead of waiting for the 30s cron tick. (No auth required.)
POST /wallethero-api/__test__/clients/set-idpos-idSeed the IDPoS↔WalletHero mapping: upsert the client identity and dual-write the legacy idpos_customer_id column. Body: client_id, idpos_customer_id.

No SDK methods — test harness only.

WalletHero Documentation