affiliatecom/zeroclick-sdk-sellers-php

Unofficial, third-party PHP seller SDK for ZeroClick.ai. Not affiliated with or endorsed by ZeroClick.

Maintainers

Package info

github.com/affiliatecom/zeroclick-sdk-sellers-php

Documentation

pkg:composer/affiliatecom/zeroclick-sdk-sellers-php

Transparency log

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.1 2026-08-04 07:17 UTC

This package is auto-updated.

Last update: 2026-08-04 07:17:59 UTC


README

CI

Accept agent traffic forwarded by ZeroClick in a PHP API: verify that the traffic is genuine, check that the buyer's plan covers the work, do the work, and settle what was consumed.

⚠️ Unofficial package

This is an unofficial, third-party SDK published by Affiliate.com. It is not affiliated with or endorsed by ZeroClick, and ZeroClick does not produce, review, or support it. Nothing here is a ZeroClick asset or wordmark. If you need a vendor-supported client, ask ZeroClick for one — they ship TypeScript, Python, and Go.

What it does

An agent calls your API through ZeroClick. Four things have to happen, in this order, and this package does each of them:

Step What it means How
Verify The request really came from ZeroClick and reached you unaltered HMAC over six canonical fields, locally, with no network call
Check The buyer's plan covers the work you are about to do POST /v1/usage/check, before anything billable starts
Serve Your handler does the work Nothing of ours in the way
Settle Report what was actually consumed On the response, or asynchronously afterwards

A refused request is a decision, not an exception: the guard hands back a ready-to-return 401, 402, or 503, so your code reads as a guard clause rather than a try/catch around ordinary traffic.

Requirements

  • PHP 8.3 or later (tested on 8.3, 8.4, and 8.5)
  • Any PSR-18 HTTP client and PSR-17 factories — yours, or auto-discovered
  • No framework required. PSR-15 middleware, a Laravel bridge, and a Symfony bundle are included, and each is optional.

Install

composer require affiliatecom/zeroclick-sdk-sellers-php

If you have no PSR-18 client or PSR-17 factories installed yet, add one of each — for example:

composer require guzzlehttp/guzzle nyholm/psr7

Sixty-second quickstart

Configure the credentials ZeroClick issued you:

# Two values, not one: the key id, a colon, then the signing secret.
ZEROCLICK_SIGNING_SECRETS=<key-id>:<signing-secret>
ZEROCLICK_API_KEY=zc_live_your_api_key

The key id is the second half of the signing credential, issued beside the secret and shown with it in the ZeroClick dashboard. Pasting the secret in on its own is the most common setup mistake — see configuration.

Then wrap the billable route in the guard. One line of wiring performs the whole sequence in the order that matters, so no route can get that order wrong or forget the last step:

use AffiliateCom\ZeroClick\Sellers\Http\GuardMiddleware;
use AffiliateCom\ZeroClick\Sellers\Usage\UsageItem;

// $productWatch is your own PSR-15 handler; $client is the SellerClient.
$guard = new GuardMiddleware($client, 'product-watch', [UsageItem::of('requests', 1)]);

$response = $guard->process($request, $productWatch);

A denied request comes back as the refusal, the handler never runs, and nothing is billed. A served one comes back carrying zc-usage, which is what turns the work into revenue.

The full wiring — building the client, the identity guard for free endpoints, ceilings, and asynchronous reports — is in the quickstart.

Three things that cost money quietly

Each of these is a silent, expensive failure rather than an error you will see in a log.

The default serves work you may never bill. When the allowance API gives no usable answer, this package serves the request anyway (OutagePolicy::Allow), matching every reference SDK. That keeps a ZeroClick outage from becoming your outage, and the cost is traffic authorised by nobody that may never be paid for. Watch for it with onAllowanceUnavailable, or choose OutagePolicy::Deny if refusing customers costs you less than serving them for free.

An unreported ceiling settles at zero. A maxQuantity authorises up to a limit and charges whatever amount you settle. Deliver the response without settling and that amount is zero — free for the buyer, unbilled for you, with no error anywhere. The report is what turns the work into revenue. Use a fixed quantity whenever the size is known before the work starts.

A normalised request target fails verification, and a proxy can normalise it before PHP sees it. ZeroClick signs the percent-encoded path and query exactly as it sent them. A framework — or an ingress — that decodes or reorders them breaks verification for those URLs only, in production only, presenting as an unexplained 401. This package detects the framework case and faults loudly. The proxy case no PHP change can fix: nginx and several managed load balancers normalise %2F by default, and this can appear on a working deployment with no application change at all. See the raw request target for what to check.

Documentation

Document What is in it
Quickstart Verify, check, serve, settle — end to end
Configuration Every option: type, default, environment fallback, when to change it
API reference Every public class and method, with signatures and examples
Errors The fault hierarchy, decisions versus faults, every error code
Middleware PSR-15, Laravel, and Symfony wiring
The raw request target The highest-cost failure mode, and what to check
Read-only catalog Sellers, services, meters, plans — unverified surface
Read-only analytics Overview, revenue, transactions — unverified surface
Examples Runnable PSR-15, Laravel, and Symfony integrations

Limitations

Stated plainly, because the alternative is that you find out later.

  • The catalog and analytics surfaces are unverified against a running server. Their paths, trailing slashes, and response shapes come from ZeroClick's OpenAPI document and nothing else; no shipped client exercises them. Everything else in this package is corroborated by at least two sources. Confirm them against a live sandbox before you depend on them.
  • The body-encryption parameters were recovered from reference SDK source, not from a specification. ZeroClick documents none of that surface — the suite, the curve, and the custom reply-key header were read out of shipped code. The suite is pinned rather than negotiated, and the whole feature sits behind an optional dependency you install on purpose.
  • Neither of those surfaces exists in any other ZeroClick SDK. Their presence here is a deliberate addition, not parity, and must not be read as a claim that a TypeScript, Python, or Go equivalent exists.
  • Upstream is pre-1.0, and the reference SDKs already disagree with each other. The TypeScript, Python, and Go clients differ on environment-variable names, on where the service slug lives, and on how error codes are spelled. In places ZeroClick documents two incompatible conventions at once. Where they conflict this package follows the shipped code, and says so at the point it matters — in configuration, the API reference, and errors. Expect upstream to move.
  • Management API writes, organizations, API-key management, Stripe Connect, sandbox control, and the agent-facing pay-URL surface are out of scope. Sellers never call them.

Versioning

Semantic Versioning. What the commitment covers — and what it deliberately does not — is stated in the changelog. Read that before pinning: the upstream protocol is outside our control, and we do not pretend otherwise.

Contributing

One command per task, and ci runs locally exactly what CI runs:

composer test      # the PHPUnit suite
composer analyse   # PHPStan, level 10, strict rules, no baseline
composer lint      # formatting check
composer fix       # apply formatting
composer ci        # everything CI runs, in the same order

The suite is fully offline — every outbound call goes through a recording PSR-18 fake — so nothing here depends on an external service.

Documentation is executed, not reviewed. Every PHP sample on every page in docs/, and in this README, is extracted and run against that same fake, so a signature change breaks the build instead of quietly rotting the docs. Test coverage is measured by an actual run in CI (composer test:coverage); no coverage figure is written here, because a number typed into a file is not a measurement.

Licence

Apache-2.0. See LICENSE.