Skip to main content
The zeroclick-sellers gem implements the ZeroClick billing guard for Ruby backends: it verifies that each request came from ZeroClick, checks the buyer’s allowance before you do the work, returns the refusal responses ZeroClick expects, and settles or reports what was used. The integration overview describes the contract it implements. It ships Rack middleware, so one implementation covers Rails, Sinatra, Hanami and Roda — Rails is a Rack app.

Install

The gem requires Ruby 3.1 or later and has no runtime dependencies: the core is stdlib-only (openssl, json, net/http), and the middleware is duck-typed rather than requiring rack. Adding it cannot change how anything else in your bundle resolves.
You need three values from the dashboard: your signing secret (zcsec_…), its key id (hsec_…), and an API key (zc_…) with the usage:read and usage:write scopes. See keys and secrets.
ZEROCLICK_SIGNING_SECRETS holds kid:secret pairs, comma-separated. It is a map rather than a single secret so that during a rotation both the old and the new kid verify — otherwise every in-flight request fails the moment you rotate.

Guard a Rails route

The Railtie is opt-in, so the gem stays usable without Rails on the load path.
Do not pass seller: ZeroClick::Sellers.seller here. config.middleware.use evaluates its arguments in the Application class body, which runs before the initializer that reads config.zeroclick — so no client exists yet and boot fails. Omit seller: and the middleware resolves the process-wide client on the first request, by which point configuration exists. Outside Rails, where you build the client yourself, pass it explicitly.
In development only, ActionDispatch::HostAuthorization sits at the top of the Rails stack and answers 403 before the guard is reached, so testing through a tunnel needs config.hosts << "my-tunnel.ngrok.app". It is not in the production stack, so a deployed app needs nothing here.
Inside a controller, the verified caller is on the request env:
zc_request_id correlates with ZeroClick’s own logs — use it when you derive an idempotency key.

Guard a Sinatra or Rack route

The same middleware, mounted the Rack way:

What the middleware does

  1. Reads the body (bounded at 10 MiB) and verifies zc-signature before anything else.
  2. Asks ZeroClick whether the buyer can pay for the declared usage.
  3. On refusal, answers 402 with the payment challenge — your app is never called.
  4. On success, calls your app, then attaches zc-usage only if you answered 2xx.
A non-2xx answer is never billed: the work was not delivered. Your app can still read the request body. Verification has to consume it, so the middleware replaces rack.input with a fresh stream over the same bytes. It deliberately does not rely on #rewind, which Rack 3 no longer guarantees.

Guard without the middleware

When the charge depends on the request, guard inside the action:
For a free endpoint that still needs to know who is calling, use Middleware::Identify or SELLER.guard_identity. Neither makes a network call.

Deployment note

The signature covers the raw, percent-encoded request target. The middleware prefers the server’s raw target (REQUEST_URI, which Puma sets) over the decoded PATH_INFO. Behind a server that decodes and exposes no raw target, a route containing an encoded separator such as %2F cannot verify — deploy behind Puma if your routes can contain one.

Next

Configuration

Keys, the outage policy, and timeouts.

Middleware

Meter, Identify, and settling variable usage.

API

Every public method and value type.

Errors

What raises, what returns a decision.