Sync customers
Keep a table of who holds your tool on your own server, with webhooks and the API.
Your server may need to know who holds a tool: to unlock a web app the extension pairs with, to send onboarding email, or to reconcile with a CRM. This guide keeps a customers table current with webhooks, and corrects it with the API.
Before you begin
- An API key from Configure → API.
- A server that can receive HTTPS requests. For development,
npx -y toolaby@latest webhooks triggerreaches a server on your machine; see Webhooks.
1. Load the table
Page through the tool's customers once and store each.
let cursor = null;
do {
const url = new URL(`https://${workspace}.toolaby.app/api/v1/tools/${tool}/customers`);
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('cursor', cursor);
const page = await (await fetch(url, { headers: { Authorization: `Bearer ${key}` } })).json();
for (const c of page.data) await db.customers.upsert({ email: c.email, entitled: c.entitled, via: c.via });
cursor = page.next_cursor;
} while (cursor);2. Keep it current with webhooks
Register your endpoint under Configure → Webhooks and select all six events. On each delivery, verify the signature, then apply the change:
| Event | Change |
|---|---|
purchase.completed | entitled: true, via from kind (licence or subscription). For a licence bought signed out (account: 'none'), the address is the key. |
subscription.started | entitled: true, via: 'subscription'. |
subscription.cancelled | With immediately: true, entitled: false. Otherwise access continues until ends_at: keep entitled: true and record the date. |
payment.refunded | entitled: false for that licence. |
payment.disputed | entitled: false while the dispute is open. |
dispute.closed | won or warning_closed: entitled: true. lost: entitled: false. |
Store the event id and ignore a delivery whose id you have seen. Answer 2xx before writing to the database.
Grants and trials made in the dashboard or by the API are not events. Step 3 covers them.
3. Correct with the API
Webhooks tell you what changed; the API tells you what is. Once a day, or before an action that matters, retrieve the customer:
const c = await (await fetch(`https://${workspace}.toolaby.app/api/v1/tools/${tool}/customers/${encodeURIComponent(email)}`, {
headers: { Authorization: `Bearer ${key}` },
})).json();
await db.customers.upsert({ email, entitled: c.entitled, via: c.via });entitled is the truth at that moment, including grants, trials and bundles. A workspace makes 20,000 lookups a day; to correct more customers than that, page through the list as in step 1.
Notes
- A team licence entitles the seat holders, not the buyer; each holder appears as a customer with
via: 'licence'. - A buyer who changes their address keeps their account; the API answers for the new address.
- Deliveries can arrive out of order. When two events concern the same subscription, the later
createdwins.