> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zeroclick.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Go SDK middleware

> What the Go SDK's Meter and Identify middleware do on every request: body handling, 2xx-only settlement, streaming support, and failure modes.

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`.

```go theme={null}
// Billable: one fixed charge per request.
mux.Handle("/v1/product-watch", seller.Meter(sellers.PerRequest("requests", 1))(
	http.HandlerFunc(productWatch)))

// Free, but identity-scoped: the caller must prove which buyer they are.
mux.Handle("/v1/limits", seller.Identify()(http.HandlerFunc(limits)))
```

`Meter` needs `ServiceSlug` set on the client's [Config](/sdks/go/configuration); 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.

| Behavior                                         | Why it matters                                                                                                                                                                                                                          |
| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Restores `r.Body` after verification             | The signature covers the raw bytes, so verifying consumes the body. Without this, your handler reads empty, and does so silently.                                                                                                       |
| `zc-usage` only on a 2xx                         | A 4xx is the buyer's bad input and a 5xx is your failure. Neither should bill.                                                                                                                                                          |
| Bounds the body (`MaxBodyBytes`, default 10 MiB) | Verification needs the whole body in memory, and an attacker does not need a valid signature to make you buffer it. Over the cap, the middleware refuses the request with `413 {"error":"request_body_too_large"}` before verification. |
| Puts the buyer in the request context            | `sellers.FromContext(r.Context())` returns the verified `AgentID` and `RequestID`.                                                                                                                                                      |
| Refuses before your handler runs                 | The middleware refuses an unsigned request before it ever consults the allowance API, and refuses an unpayable one before your code runs. Unpayable traffic never costs you work.                                                       |

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](/integrate/overview) 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:

```go theme={null}
handler := func(w http.ResponseWriter, r *http.Request) {
	zc, _ := sellers.FromContext(r.Context())

	tokens := 1842 // discovered by doing the work

	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, `{"outputTokens":%d}`, tokens)

	// Reported AFTER the response. Two things matter here:
	//
	//  1. BackgroundContext, not r.Context(): the request context is
	//     already cancelled, so the report would be silently dropped and
	//     the tokens would go unbilled.
	//  2. An idempotency key derived from the request id, never random:
	//     a retried report with a fresh key double-bills.
	_, err := seller.ReportUsage(sellers.BackgroundContext(r), sellers.ReportUsageInput{
		AgentID:        zc.AgentID,
		ServiceSlug:    "product-watch",
		MeterSlug:      "output_tokens",
		Quantity:       tokens,
		IdempotencyKey: zc.RequestID + "_output_tokens",
	})
	if err != nil {
		// Delivered but unbilled. Log it; do not fail a request the
		// buyer already has.
		log.Printf("output_tokens not reported: %v", err)
	}
}

http.Handle("/v1/product-watch", seller.Meter(sellers.PerRequest("requests", 1))(
	http.HandlerFunc(handler)))
```

Two things are easy to get wrong:

<Warning>
  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.
</Warning>

<Warning>
  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.
</Warning>

To let the buyer authorize a variable amount up front instead, see [charge up to a maximum](/integrate/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`:

```go theme={null}
func limits(w http.ResponseWriter, r *http.Request) {
	zc, _ := sellers.FromContext(r.Context())
	w.Header().Set("content-type", "application/json")
	json.NewEncoder(w).Encode(map[string]any{"zcAgentId": zc.AgentID})
}
```

See [free and identity endpoints](/integrate/free-and-identity-endpoints) for when to use this over an unguarded route.

## Failure modes

| Condition                                                                                                              | Response                                                                                                                                         |
| ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Body exceeds `MaxBodyBytes`, or the client hung up mid-body                                                            | `413 {"error":"request_body_too_large"}`. The SDK cannot verify a signature over a body the server does not fully have.                          |
| Signature missing, malformed, stale, or wrong                                                                          | `401 {"error":"invalid_zeroclick_signature"}`                                                                                                    |
| Allowance denied                                                                                                       | The priced `402 payment_required` body.                                                                                                          |
| Allowance API unreachable                                                                                              | Governed by `Policy`: served (default), `503 {"error":"allowance_unavailable"}`, or treated as an error.                                         |
| Guard returned an error: a malformed usage list, a signing-secret resolver fault, or `PolicyThrow` surfacing an outage | `500 {"error":"internal_error"}`, with the underlying error written to the configured `Logger`. Neither is the buyer's fault, and neither bills. |

The full split between decisions and errors is on the [errors page](/sdks/go/errors).

## Deploying behind a proxy

<Warning>
  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.
</Warning>

The [signature spec](/integrate/signature-spec) specifies the exact bytes the signature covers.
