Toolaby Wall

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 trigger reaches 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:

EventChange
purchase.completedentitled: true, via from kind (licence or subscription). For a licence bought signed out (account: 'none'), the address is the key.
subscription.startedentitled: true, via: 'subscription'.
subscription.cancelledWith immediately: true, entitled: false. Otherwise access continues until ends_at: keep entitled: true and record the date.
payment.refundedentitled: false for that licence.
payment.disputedentitled: false while the dispute is open.
dispute.closedwon 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 created wins.

On this page