Webhooks
Charm can push loyalty events to your server the moment they happen, so an
ERP, CDP or a no-code tool like Make or Zapier reacts in seconds instead of
polling /customers on a schedule.
Topics
Section titled “Topics”| Topic | Fires when |
|---|---|
points.earned | A customer earns points (orders, rules, API awards) |
points.adjusted | A balance is corrected or deducted — points is signed, negative for deductions |
reward.redeemed | Points are spent on a reward |
tier.changed | A customer reaches a new VIP tier |
tier.approaching | A customer gets close to the next tier |
store_credit.issued | Store credit lands on a customer’s account |
referral.completed | A referred friend’s qualifying order completes |
Subscribe
Section titled “Subscribe”Webhooks are managed over the API with a key that carries the
webhooks:manage scope:
POST /api/v1/webhooks{ "url": "https://example.com/charm-hook", "topics": ["points.earned", "tier.changed"]}The response includes the endpoint’s signing secret exactly once — store
it; it is never shown again (lose it and you delete + recreate the endpoint,
which costs nothing). GET /webhooks lists your endpoints with delivery
health, DELETE /webhooks/{id} stops deliveries. A store can hold up to 10
endpoints, and the URL must be public https.
What a delivery looks like
Section titled “What a delivery looks like”POST https://example.com/charm-hookContent-Type: application/jsonX-Charm-Topic: points.earnedX-Charm-Event-Id: evt_kFb2p9qArM3wX-Charm-Hmac-Sha256: q1nZ…=Charm-Version: 2026-10-01
{ "id": "evt_kFb2p9qArM3w", "topic": "points.earned", "created_at": "2026-08-20T13:00:00.000Z", "shop": "your-store.myshopify.com", "payload": { "customer_id": "gid://shopify/Customer/7712345", "customer_legacy_id": 7712345, "points": 120, "balance_after": 340, "source": "order" }}payload.customer_id is the Shopify customer GID, the same id the
customer endpoints return and accept;
payload.customer_legacy_id is the numeric id. Every topic’s payload, field by
field: Webhook events.
Charm-Version is the API version the endpoint was created
with; the body is always rendered in it, so a receiver never sees a breaking
change it did not opt into.
Verify the signature
Section titled “Verify the signature”X-Charm-Hmac-Sha256 is the base64 HMAC-SHA256 of the raw request body,
keyed with the endpoint’s secret. Verify before trusting anything:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, header, secret) { const expected = createHmac("sha256", secret).update(rawBody, "utf8").digest("base64"); const a = Buffer.from(expected); const b = Buffer.from(String(header || "")); return a.length === b.length && timingSafeEqual(a, b);}Compute it over the raw bytes — re-serializing the parsed JSON will produce a different string and a false mismatch.
Delivery semantics
Section titled “Delivery semantics”- Acknowledge fast. Respond with any
2xxwithin 5 seconds. Do the real work after responding if it might take longer. - At-least-once. A delivery that isn’t acknowledged is retried with
backoff (1 min → 24 h, 7 attempts over ~33 hours), then dropped. Duplicates
are possible — dedupe on the event
id, which stays the same across every retry. - Redirects are not followed. Register the final URL.
- Failing endpoints switch off. After 25 consecutive failed attempts the
endpoint is disabled (
GET /webhooksshowsdisabled_reason). Turn it back on in the developer console once your receiver is healthy, or delete and recreate it.
Test events and replays
Section titled “Test events and replays”Settings → API → Developer console shows each endpoint’s last 20 deliveries with their attempts and the response your server gave.
- Send test event delivers a sample of a subscribed topic. Its event id
starts with
evt_test_and its payload carries"test": true, with a fictional customer: skip such events in production logic. - Send again re-sends a past delivery with the same event id. A receiver that already processed it deduplicates; one that failed gets another chance.
Related
Section titled “Related”- API overview — keys, scopes, rate limits, idempotency
- MCP for AI assistants — the same events, asked about in natural language