ClocknextHelp
DocumentationAPI ReferenceMCP Tools
Dashboard

Get started

OverviewQuickstart

Good to know

Response formatHandling errorsPaginating resultsSending data reliablyYour own customer idsWebhooks

API reference

Record credit POSTRecord outcome POSTComplete outcome POSTRecord wallet POSTRecord unit POSTComplete composite POST
List customers GETCreate a customer POSTGet a customer GETUpdate a customer PATCHDelete a customer DELETEGet a customer's balances GETGet a customer's wallet ledger GETSet a customer's low-balance alert PUTGet a customer's current plan GETChange a recurring grant PUTList Customer Usage Log GETLook Up Usage Logs by ID GET
Manage a customer's credit balance POSTManage a customer's outcome balance POSTManage a customer's unit balance POSTManage a customer's composite balance POSTManage a customer's wallet balance POST
List a customer's members GETAdd a member POSTUpdate a member PATCHRemove a member DELETE
PlansCreditsOutcomesUnitsComposites
List taxes GETCreate a tax POSTGet a tax GETUpdate a tax PATCH
List purchases GETCreate a purchase POSTGet a purchase GETUpdate a purchase's auto-payment setting PATCHCancel a purchase POST
List invoices GETGet an invoice GETMint a hosted pay link GET
List payments GETGet a payment GET
wallet.balance.update POSTwallet.balance.drop POST
Mint a customer-portal access token POST

API Reference

Your own customer ids

Give a customer an externalId — the id your product already uses — and name it by that instead of ours.

Every customer has our id (customerId, e.g. cus_abc123). If your own product already has an id for that customer — a user id, an account number — you can store it on the customer as its externalId. After that, signals and new purchases can name the customer by externalId instead of customerId, and you can fetch or update the customer by it.

The rules

  • 1–32 characters: letters, numbers and - only (acme-42, user-9f21). Anything else is a 400: externalId must be 1–32 characters: letters, numbers and "-" only.
  • Matched exactly — Acme-42 and acme-42 are different ids.
  • Unique among your active customers. Using one another active customer already holds is a 409: Another customer already uses external id "acme-42". An archived customer's id can be reused.
  • Optional. A customer without one returns externalId: null.

Setting it

Send externalId when you create or update a customer. On update, null clears it. Every customer object returns it.

curl -X POST https://payments.clocknext.com/api/v1/customers \
  -H "Authorization: Bearer $CLOCKNEXT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme Inc", "email": "ops@acme.com", "externalId": "acme-42" }'
import { ClockNext } from "@clocknext/sdk";

const cnk = new ClockNext({ apiKey: process.env.CLOCKNEXT_API_KEY! });

const customer = await cnk.customers.create({
  name: "Acme Inc",
  email: "ops@acme.com",
  externalId: "acme-42",
});

// Later: change it, or clear it with null.
await cnk.customers.update(customer.id, { externalId: null });

Using it instead of customerId

These request bodies take an externalId field in place of customerId: all signals (credit, outcome, outcome complete, unit, wallet, composite complete) and create a purchase.

Everything else still takes our customerId only — including mint a portal token, the customer usage log, and the customerId filter on list invoices, list payments and list purchases. To use those with your own id, look the customer up first with GET /customers/ext/{externalId} and use its id.

If you send both, customerId wins

When a request carries both customerId and externalId, customerId is used and externalId is ignored — they are not checked against each other. Send neither and you get a 400: customerId: Provide customerId or externalId. An externalId that matches no active customer is a 404: No customer with external id "acme-42".

# A signal, naming the customer by your id
curl -X POST https://payments.clocknext.com/api/v1/signal/unit \
  -H "Authorization: Bearer $CLOCKNEXT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "externalId": "acme-42", "agentKey": "seats", "quantity": 3 }'

# A purchase
curl -X POST https://payments.clocknext.com/api/v1/purchases \
  -H "Authorization: Bearer $CLOCKNEXT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "externalId": "acme-42", "planId": "plan_growth" }'
await cnk.signals.unit({ externalId: "acme-42", agentKey: "seats", quantity: 3 });

await cnk.purchases.create({ externalId: "acme-42", planId: "plan_growth" });

// Endpoints that take customerId only: look the customer up first.
const customer = await cnk.customers.getByExternalId("acme-42");
const invoices = await cnk.invoices.list({ customerId: customer.id });

Signals are checked later

Signals are processed in the background, so a signal whose externalId matches no customer is still accepted (202). It then sits as Pending with the 404 reason on the Signals page. Create the customer (or set its externalId) and retry it.

Addressing a customer by it

These routes take the external id right in the path. Each behaves exactly like its /customers/{id} twin — same body, same response — and is documented on the same page, in the "using external Id" section:

RoutePage
GET /customers/ext/{externalId}Get a customer
PATCH /customers/ext/{externalId}Update a customer
DELETE /customers/ext/{externalId}Delete a customer
GET /customers/ext/{externalId}/balancesGet a customer's balances
GET /customers/ext/{externalId}/planGet a customer's current plan
PUT /customers/ext/{externalId}/recurring-grantChange a recurring grant
POST /customers/ext/{externalId}/credits/adjustManage a customer's credit balance
POST /customers/ext/{externalId}/outcomes/adjustManage a customer's outcome balance
POST /customers/ext/{externalId}/units/adjustManage a customer's unit balance
POST /customers/ext/{externalId}/composites/adjustManage a customer's composite balance
POST /customers/ext/{externalId}/wallet/adjustManage a customer's wallet balance

A malformed {externalId} is a 400; one matching no active customer is a 404. Every other /customers/{id}/… route — the wallet read and members — still takes our id only.

curl https://payments.clocknext.com/api/v1/customers/ext/acme-42 \
  -H "Authorization: Bearer $CLOCKNEXT_API_KEY"

curl -X POST https://payments.clocknext.com/api/v1/customers/ext/acme-42/credits/adjust \
  -H "Authorization: Bearer $CLOCKNEXT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "agentKey": "deep-research", "delta": 500 }'
const customer = await cnk.customers.getByExternalId("acme-42");

await cnk.customers.updateByExternalId("acme-42", { currencyCode: "USD" });

await cnk.customers.updateRecurringGrantByExternalId("acme-42", {
  kind: "wallet",
  quantity: 10000,
});

await cnk.customers.adjustCreditByExternalId("acme-42", { agentKey: "deep-research", delta: 500 });

await cnk.customers.adjustWalletByExternalId("acme-42", { delta: 250 });

Reading it back

Customer objects carry externalId (or null when unset). Invoice, payment and purchase rows do not — they carry the customer's customerId.

Sending data reliably

How the SDK sends signals — buffered by default, with retries, idempotency, and the flush() you must call before a serverless function exits.

Webhooks

How webhook events reach your server — the request, verifying the signature, responding, retries and duplicates.

On this page

The rulesSetting itUsing it instead of customerIdAddressing a customer by itReading it back