net/http middleware. Meter guards a billable endpoint; Identify guards a free endpoint that must still know which buyer is calling. Both return a plain func(http.Handler) http.Handler, the standard middleware shape, so they compose with net/http, chi, gorilla/mux, and anything else built on http.Handler.
Meter needs ServiceSlug set on the client’s Config; it reads the service, plan, and body limit from there.
What Meter does on every request
In order: it reads the request body under the configured cap, verifies thezc-signature header over the raw bytes, and checks the buyer’s allowance for the declared usage. Only then does it call your handler, with the body restored and the buyer in the request context. When your handler responds 2xx, it attaches the zc-usage header so the request settles.
A refusal is the exact response ZeroClick expects:
401 {"error":"invalid_zeroclick_signature"} for a failed verification, the priced 402 payment_required body for an allowance denial, or 503 {"error":"allowance_unavailable"} under a fail-closed outage policy. See integrate your API for the bodies and when each one is returned.
Fixed quantities only
Meter settles exactly what it declares, so it takes fixed quantities only: sellers.PerRequest(meter, n) items. If handed an UpTo item or a non-positive quantity, it panics at wire-up. A ceiling has no settled quantity: it would bill zero, silently, on a delivered 200, which is the exact failure this SDK exists to prevent. The panic happens when you build the middleware, not in production traffic.
For work you cannot size up front, declare the fixed part to Meter and report the variable part after responding.
Billing for work you can’t size up front
Declare a fixed charge to the guard: that is what lets ZeroClick offer theexact payment scheme, which is the one clients can pay today. Then report the variable part with ReportUsage after the response has gone out. This example charges one requests unit per call synchronously and reports output_tokens afterwards:
Guard directly rather than Meter.
Settling on 2xx only
The middleware wraps your response writer and defers thezc-usage header until the status is known, because only a delivered response should be billed. A 4xx means you rejected the buyer’s input, and a 5xx is your own failure. Charging for either bills someone for work they did not receive.
Two edge cases the wrapper handles for you:
- A handler that writes nothing still delivers a 200 (
net/httpemits it at the transport layer, below the wrapper), so the middleware settles it explicitly rather than letting the work ship unbilled. - A handler that sets its own
zc-usageheader wins: the middleware attaches the declared usage only when the header is not already present, so you can override the settled quantities for one response when you must.
Streaming and WebSockets
The wrapped writer implementshttp.Flusher, http.Hijacker, and Unwrap (for http.ResponseController), so streaming handlers work unchanged:
- Server-sent events: a direct
w.(http.Flusher)assertion, the long-standing streaming idiom, succeeds.Flushsettles the usage header before the first byte leaves, because headers cannot follow the body. - WebSocket upgrades:
Hijackworks, but a hijacked connection leaves HTTP response semantics behind, so nozc-usageheader can follow it. Meter the connection itself as the fixed charge and report anything per-message withReportUsage.
Identify
Identify guards a free endpoint that must still know which buyer is calling, such as a limits or account route. It bounds and restores the body and verifies the signature exactly as Meter does, but makes no network call and settles nothing.
A signed request with no zc-agent-id header is an anonymous probe: valid, but it has not established a buyer. Identify answers it with a 402 carrying an empty usage list. That makes ZeroClick issue a $0 identity challenge and retry the request with the identity attached. Your handler then finds the buyer with sellers.FromContext:
Failure modes
The full split between decisions and errors is on the errors page.