Cairn Developer API and webhooks
Last updated 2 November 2026
Every Cairn plan includes a REST API and signed webhooks. Use them to show points in a headless storefront, answer "how many points do I have?" inside a help desk, post points from your own systems, or start automations in Zapier, Make, n8n or your own code. Create the API token and the webhook endpoint in the app under Integrations.
Authentication
Send the token as a bearer token. It identifies your shop, so no other header is needed. Keep it on a server: the API sends no CORS headers because the token must never reach a browser. Creating a new token replaces the old one immediately.
curl https://cairn.keelcroft.com/api/v1/members?email=ada@example.com \ -H "Authorization: Bearer cairn_..."
Responses are JSON. Errors look like {"error":{"code":"member_not_found","message":"..."}}. Limit: 300 requests a minute per store.
Endpoints
GET /api/v1/programpoints name, earning rate, reward ladder and tiers.GET /api/v1/members?email=or?customer_id=the member: points available and pending, tier, referral link, and which rewards they can afford.GET /api/v1/members/{customer_id}the same, by Shopify customer id.GET /api/v1/members/{customer_id}/ledger?limit=50every points entry, newest first (up to 200).POST /api/v1/members/{customer_id}/pointsbody{"points": 250, "reason": "Survey"}. Positive or negative whole numbers, up to 1,000,000. Requires anIdempotency-Keyheader: repeating a request with the same key never posts twice.POST /api/v1/members/{customer_id}/redeembody{"reward_id": "..."}using an id from the member'srewards, or{"points": 500}when variable redemption is on. Returns the code, or the store credit issued.
Customers join the program when they create an account or order, exactly as in the Shopify admin. The API reads and changes existing members; it does not create them.
Webhooks
Cairn sends an HTTPS POST for each event below. Failed deliveries are retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours; after that you can retry from the app. Respond with any 2xx status within 8 seconds. Redirects are not followed.
member.joinedpoints.earnedreward.unlockedreferral.rewardedtier.changedpoints.expiring_30_dayspoints.expiring_7_daysbirthday.rewardedreward.redeemedmember.lapsedtest.pingsent by the Send test event button
{
"id": "evt_6f1c...",
"type": "points.earned",
"created_at": "2026-11-02T09:30:00.000Z",
"shop": "your-store.myshopify.com",
"data": {
"customer": { "id": 123456, "gid": "gid://shopify/Customer/123456",
"email": "ada@example.com", "first_name": "Ada",
"accepts_marketing": true },
"points": 120, "balance": 620, "points_name": "stars",
"tier": "Silver", "referral_link": "https://your-store.com/?ref=ADA1"
}
}An event can arrive more than once; use id to ignore repeats. Order is not guaranteed.
Verifying the signature
Each request carries Cairn-Signature: t=<unix seconds>,v1=<hex>. The value is HMAC-SHA256 of t + "." + raw body using your webhook secret (the whsec_ value shown once when you save the endpoint). Reject requests whose timestamp is more than five minutes old.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
const expected = createHmac("sha256", secret)
.update(parts.t + "." + rawBody)
.digest("hex");
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
return ok && Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
}Headers also include Cairn-Event (the type) and Cairn-Delivery (the event id).
Help desks and automation tools
Tools that call a URL with a header (Gorgias HTTP integrations, Zapier "Webhooks", Make HTTP, n8n) work with the member endpoint and the token above. Tools that receive a URL (Zapier "Catch Hook", Make webhooks, n8n) can take the webhook endpoint; they do not need to check the signature, but anyone who learns the URL could post to it, so prefer tools that let you verify it.