For developers

Loyalo API & webhooks

Connect your own systems to Loyalo: subscriptions, customers and points over REST, plus signed webhooks with curl and JavaScript examples.

Authentication

Create a key in Loyalo under Settings → API & webhooks. Send it in the x-api-key header (or as a Bearer token). A key always belongs to a single shop and only works for the scopes you enable. Keys cannot be retrieved after creation: store them safely on your side.

curl "https://mwugkfkidoakjoelhtir.supabase.co/functions/v1/v2-api/ping" \
  -H "x-api-key: lo_live_xxxxxxxxxxxxxxxx"

Endpoints

All responses are JSON shaped as { ok: true, data: … }. Lists also return next_offset (null on the last page). Errors return { error: "…" } with a matching status code.

GET/subscriptionsscope: subscriptions

List your shop's subscriptions, newest first.

Query parameters: status (active | paused | cancelled | expired | failed), customer_id, email, limit (max 200), offset

curl "https://mwugkfkidoakjoelhtir.supabase.co/functions/v1/v2-api/subscriptions" \
  -H "x-api-key: $LOYALO_API_KEY"
GET/subscriptions/{id}scope: subscriptions

A single subscription with lines, amount and next billing date.

curl "https://mwugkfkidoakjoelhtir.supabase.co/functions/v1/v2-api/subscriptions/SUBSCRIPTION_ID" \
  -H "x-api-key: $LOYALO_API_KEY"
POST/subscriptions/{id}/pausescope: subscriptions:write

Pauses the subscription (both Shopify and Mollie).

curl -X POST "https://mwugkfkidoakjoelhtir.supabase.co/functions/v1/v2-api/subscriptions/SUBSCRIPTION_ID/pause" \
  -H "x-api-key: $LOYALO_API_KEY" \
  -H "Content-Type: application/json"
POST/subscriptions/{id}/resumescope: subscriptions:write

Resumes a paused subscription.

curl -X POST "https://mwugkfkidoakjoelhtir.supabase.co/functions/v1/v2-api/subscriptions/SUBSCRIPTION_ID/resume" \
  -H "x-api-key: $LOYALO_API_KEY" \
  -H "Content-Type: application/json"
POST/subscriptions/{id}/skipscope: subscriptions:write

Skips the next delivery; the date moves one interval forward.

curl -X POST "https://mwugkfkidoakjoelhtir.supabase.co/functions/v1/v2-api/subscriptions/SUBSCRIPTION_ID/skip" \
  -H "x-api-key: $LOYALO_API_KEY" \
  -H "Content-Type: application/json"
POST/subscriptions/{id}/reschedulescope: subscriptions:write

Moves the next delivery to another date (within a year).

curl -X POST "https://mwugkfkidoakjoelhtir.supabase.co/functions/v1/v2-api/subscriptions/SUBSCRIPTION_ID/reschedule" \
  -H "x-api-key: $LOYALO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "date": "2026-11-15" }'
POST/subscriptions/{id}/cancelscope: subscriptions:write

Cancels the subscription permanently.

curl -X POST "https://mwugkfkidoakjoelhtir.supabase.co/functions/v1/v2-api/subscriptions/SUBSCRIPTION_ID/cancel" \
  -H "x-api-key: $LOYALO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "customer_request" }'
GET/customersscope: customers

Customers with point balance, spend and language preference.

Query parameters: email (exact match), limit (max 200), offset

curl "https://mwugkfkidoakjoelhtir.supabase.co/functions/v1/v2-api/customers" \
  -H "x-api-key: $LOYALO_API_KEY"
GET/pointsscope: points

Point transactions, optionally filtered by customer.

Query parameters: customer_id, limit (max 500), offset

curl "https://mwugkfkidoakjoelhtir.supabase.co/functions/v1/v2-api/points" \
  -H "x-api-key: $LOYALO_API_KEY"
POST/pointsscope: points:write

Add (positive) or remove (negative) points. Give the customer by customer_id (Loyalo's), shopify_customer_id or email: exactly one. The same idempotency_key (or Idempotency-Key header) never books twice.

curl -X POST "https://mwugkfkidoakjoelhtir.supabase.co/functions/v1/v2-api/points" \
  -H "x-api-key: $LOYALO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "customer_id": "…", "points": 100, "note": "Goodwill", "idempotency_key": "ticket-4711" }'
GET/redemptionsscope: redemptions

Redeemed discounts with code, status and expiry.

Query parameters: customer_id, status (pending | issued | failed | used | expired), limit (max 200), offset

curl "https://mwugkfkidoakjoelhtir.supabase.co/functions/v1/v2-api/redemptions" \
  -H "x-api-key: $LOYALO_API_KEY"
GET/pingscope: —

Validates your key and returns the shop and its scopes.

curl "https://mwugkfkidoakjoelhtir.supabase.co/functions/v1/v2-api/ping" \
  -H "x-api-key: $LOYALO_API_KEY"

Error codes

StatusMessageMeaning
401Missing API key / Invalid API keyKey missing, wrong or revoked.
403Sleutel mist recht '…'The key lacks this scope. Adjust it in Loyalo.
404Not foundUnknown path, or the object does not exist in this shop.
429Rate limit exceededMore requests than the per-minute limit. Back off and retry.
500Foutmelding in het veld errorSomething failed on our side or at Shopify/Mollie.

AI assistants (MCP)

Loyalo is also an MCP server: an AI assistant such as Claude or ChatGPT uses the same functions as tools. Connect with the address below and an API key as Bearer token. The assistant only sees the tools that key's scopes allow; give it its own key, with write scopes only if it may change subscriptions or points.

claude mcp add --transport http loyalo https://mwugkfkidoakjoelhtir.supabase.co/functions/v1/v2-api/mcp \
  --header "Authorization: Bearer lo_live_xxxxxxxxxxxxxxxx"

Webhooks

Configure an endpoint in Loyalo with the events you want. Every delivery is signed and retried on failure with increasing back-off (1, 5, 20, 60 and 240 minutes). Respond with a 2xx status within 10 seconds.

EventWhen
subscription.createdNew subscription created
subscription.updatedSubscription changed (date, lines, frequency)
subscription.pausedSubscription paused
subscription.resumedSubscription resumed
subscription.cancelledSubscription cancelled
subscription.payment_failedPayment attempt failed
points.changedA customer's point balance changed
pingTest message (button in Loyalo)

Example delivery

POST https://jouw-server.nl/loyalo-webhook
x-loyalo-event: subscription.paused
x-loyalo-timestamp: 1755763200
x-loyalo-signature: 4f3a…  (HMAC-SHA256 van "timestamp.body")

{
  "id": "7b1c…",
  "event": "subscription.paused",
  "created_at": "2026-08-21T08:00:00.000Z",
  "data": {
    "id": "9f2e…",
    "external_id": "gid://shopify/SubscriptionContract/123",
    "status": "paused",
    "source": "shopify",
    "customer_email": "klant@voorbeeld.nl",
    "amount": 24.95,
    "currency": "EUR",
    "next_billing_date": "2026-09-01T00:00:00.000Z",
    "billing_interval": "MONTH",
    "billing_interval_count": 1,
    "lines": [{ "title": "Koffie 1kg", "quantity": 1 }]
  }
}

Verifying the signature

The signature is an HMAC-SHA256 over "timestamp.body" using your endpoint secret (starts with whsec_). Compare in constant time and reject stale requests.

import crypto from "node:crypto";

// Express-voorbeeld met de ruwe body (Buffer)
function verifyLoyaloWebhook(req, secret) {
  const timestamp = req.header("x-loyalo-timestamp");
  const signature = req.header("x-loyalo-signature");
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${req.rawBody}`)
    .digest("hex");

  const ok = crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature ?? ""),
  );
  // Weiger verzoeken ouder dan 5 minuten
  const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
  return ok && fresh;
}