The API key exchange is the one endpoint you must host for ZeroClick. When a buyer purchases a plan, ZeroClick calls it to trade the buyer's identity for an API key in your system. When a plan ends, ZeroClick calls it again to revoke that key. Only ZeroClick can call this endpoint: every request is signed, and you must reject requests that fail verification.
https://{your-api}/zeroclick/api-keyCalled when a buyer purchases a plan, or extends a plan after a lapse.
Request body
{
"userId": "user_8f3kz1",
"plan": "pln_starter_monthly"
}| Field | Description |
|---|---|
userId | A stable, opaque identifier for the buyer. The same buyer always has the same userId for your service. |
plan | The ID of the plan that was purchased, as shown in the dashboard. Use it to scope the key's entitlements. |
Response 200 OK
{
"apiKey": "sk_live_b64x..."
}The key should be valid immediately: ZeroClick injects it into the buyer's very next proxied request.
https://{your-api}/zeroclick/api-keyCalled when a plan expires, is cancelled, or a renewal payment fails. The
request body is the same shape (userId and plan). Invalidate the key you
issued for that buyer and return 200 OK. After revocation, ZeroClick stops
proxying requests for that buyer until they purchase again.
Revocation is your safety net
ZeroClick stops injecting a revoked buyer's key at the proxy, but the key itself lives in your system. Treat the DELETE call as authoritative: a key that isn't revoked keeps working for anyone who holds it.
Every request to your exchange endpoint carries two headers:
| Header | Value |
|---|---|
X-ZeroClick-Timestamp | Unix timestamp (seconds) when the request was signed. |
X-ZeroClick-Signature | sha256= followed by the hex HMAC-SHA256 of "{timestamp}.{rawBody}", keyed with your service's signing secret. |
Your signing secret is shown in the dashboard under your service's settings. To verify a request:
HMAC-SHA256(signingSecret, "{timestamp}.{rawBody}") and hex-encode
it.import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyZeroClickRequest(
signingSecret: string,
rawBody: string,
timestampHeader: string,
signatureHeader: string,
): boolean {
const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestampHeader));
if (!Number.isFinite(ageSeconds) || ageSeconds > 300) return false;
const expected = `sha256=${createHmac("sha256", signingSecret)
.update(`${timestampHeader}.${rawBody}`)
.digest("hex")}`;
const a = Buffer.from(signatureHeader);
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}Never accept unsigned requests
This endpoint mints credentials. If signature verification fails or the headers are missing, return 401 and do nothing else.
ZeroClick treats any 2xx response as success and retries other responses
with exponential backoff. Make both operations idempotent:
POST for the same userId and plan should return the same
key, or a fresh key that replaces the old one. It should not create a
duplicate buyer.DELETE for an already-revoked key should still return 200 OK.If your endpoint is down during a purchase, ZeroClick keeps retrying and completes provisioning when it recovers; the buyer's proxied requests are held off until a key exists.