Skip to main content
The Go SDK packages the whole guard flow (verify, check allowance, serve, settle) as 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 the zc-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 the exact 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:
Two things are easy to get wrong:
Use sellers.BackgroundContext(r), not r.Context(). The server cancels the request context the moment your handler returns, so a report on it is dropped and the work goes unbilled with nothing in the logs.
Derive the idempotency key: zc.RequestID + "_output_tokens", never a random value. A random key double-bills on retry; a derived key makes the retry a harmless duplicate.
To let the buyer authorize a variable amount up front instead, see charge up to a maximum. That pattern uses Guard directly rather than Meter.

Settling on 2xx only

The middleware wraps your response writer and defers the zc-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/http emits 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-usage header 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 implements http.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. Flush settles the usage header before the first byte leaves, because headers cannot follow the body.
  • WebSocket upgrades: Hijack works, but a hijacked connection leaves HTTP response semantics behind, so no zc-usage header can follow it. Meter the connection itself as the fixed charge and report anything per-message with ReportUsage.

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:
See free and identity endpoints for when to use this over an unguarded route.

Failure modes

The full split between decisions and errors is on the errors page.

Deploying behind a proxy

The signature covers the raw, percent-encoded request target. The SDK reads r.RequestURI, which Go leaves untouched. But a reverse proxy, ingress, or load balancer in front of you may normalize the path (%2F becoming /) before your server sees it. If that happens, every request with an encoded path segment fails verification. If you route through nginx, an ingress controller, or a managed load balancer, confirm it passes the request target through unmodified.
The signature spec specifies the exact bytes the signature covers.