providercheck: the conformance kit
providercheck proves that your merchant-provider/v1 API behaves as the contract says, against
your sandbox, before you go live. It runs the same lines as Chargeless's live contract check,
then what a live system cannot show without moving money: a real refund and its replay, an
idempotency conflict, an over-refund your API must refuse, a real cancellation, a refused key that
stays refused, and every get_operation state. Going live needs a passed run from the last 30
days, uploaded in the panel.
It is one file with no dependencies. It runs on your machine, so it also reaches a sandbox on localhost or inside your VPN, and your sandbox secret never leaves it.
Download
Version providercheck/2, for contract revisions 1.0 to 1.2.
| System | File |
|---|---|
| macOS, Apple silicon | providercheck-darwin-arm64 |
| macOS, Intel | providercheck-darwin-amd64 |
| Linux, x86-64 | providercheck-linux-amd64 |
| Linux, ARM64 | providercheck-linux-arm64 |
| Windows, x86-64 | providercheck-windows-amd64.exe |
SHA256SUMS lists the SHA-256 of each file. Download from a terminal and check it before you run it:
# macOS on Apple silicon; use your file's name from the table
curl -fsSLO https://chargeless.io/developers/downloads/providercheck/providercheck-darwin-arm64
curl -fsSLO https://chargeless.io/developers/downloads/providercheck/SHA256SUMS
grep providercheck-darwin-arm64 SHA256SUMS | shasum -a 256 -c - # Linux: sha256sum -c -
mv providercheck-darwin-arm64 providercheck && chmod +x providercheck
./providercheck -versionOn Windows, check the file with Get-FileHash providercheck-windows-amd64.exe in PowerShell and
compare it with the line in SHA256SUMS.
The files are not signed with an Apple or Microsoft certificate. A file downloaded with curl runs
as it is; one downloaded with a browser on macOS is quarantined, and
xattr -d com.apple.quarantine providercheck releases it after you checked its SHA-256.
Before you run it
- Your API runs in front of your billing system's sandbox, where a refund and a cancellation cost nothing.
- The sandbox verifies the sandbox signing secret. Chargeless shows it once, starting
srmps_, when you first save your connection in the panel (Settings → Integrations → your connection). To get a new one, edit the connection and tick "Issue a new sandbox signing secret". Configure it on the sandbox only: your production API must refuse it. - Pick fresh fixtures in the sandbox: a customer, a refundable charge of that customer and, if you support cancellations, an active subscription of that customer. The run refunds the charge (one minor unit with partial refunds, otherwise the whole remainder) and cancels the subscription, so use new ones for every run.
- Your workspace id, shown as Workspace ID under Settings → General, and filled in for you in the command the panel shows under Conformance kit. Chargeless's idempotency keys start with it, so the kit sends them the same way.
Run it
Secrets come only from the environment, never from the command line.
export PROVIDERCHECK_SANDBOX_SIGNING_SECRET=srmps_…
# optional, when your sandbox needs them:
# export PROVIDERCHECK_BEARER_TOKEN=…
# export PROVIDERCHECK_EDGE_HEADERS='[{"name":"CF-Access-Client-Id","value":"…"}]' (at most 3)
./providercheck -sandbox \
-base-url https://sandbox.billing.example.com/supportroute \
-tenant <your workspace id> \
-customer cus_… -transaction ch_… -subscription sub_… \
-out providercheck-report.jsonFor the MCP binding, add -transport mcp and use your sandbox's MCP endpoint as -base-url.
| Flag | Use |
|---|---|
-sandbox |
Required: you confirm the target is a sandbox, because the run refunds and cancels for real |
-base-url |
Your sandbox's base URL, or its MCP endpoint with -transport mcp |
-transport |
rest (the default) or mcp |
-tenant |
Your workspace id |
-customer, -transaction, -subscription |
The fixtures; leave out -subscription if you do not support cancellations |
-out |
Where the sealed report goes (default providercheck-report.json) |
-allow-private |
A sandbox over plain http, on another port or at a private address (localhost, a VPN) |
-client-cert, -client-key |
Your own test client certificate, for a sandbox that requires mTLS |
-settle-timeout |
How long to wait for a pending outcome: default 2 min, at most 30 min |
-v |
Log every call (ids, statuses and latencies, never a body) |
-version |
Print the kit's version and exit |
The kit refuses to start with a production secret (srmp_…), without -sandbox, or with a
fixture reference the contract would refuse (a run of 13 or more digits, for example). It also
refuses an origin the report could not record, such as an IPv4 address whose digits and port join
into 13 or more digits: use a host name instead.
Read the result
It prints one line per check and a verdict, and the WARN lines before them are the calls the kit
provoked on purpose (a wrong signature, a conflict):
providercheck/2 against https://sandbox.billing.example.com over rest (run 01a1cec2-cdc0-7251-af9e-9fc989823775, contract 1.0)
reachability passed TLS and an HTTP answer from the configured endpoint, through Chargeless's egress
describe passed contract version 1.0; a wrong signature and a stale timestamp were refused
get_transaction failed schema_invalid get_transaction answered 200 with a body that breaks the contract (schema_invalid): `amount.amount_minor` is missing
X-SR-Request-Id 01a1cec2-cdc8-79eb-b918-1b84d0bab31f
…
FAILED; sealed report written to providercheck-report.json- Exit status
0means every check passed,1that one failed,2that the run could not start. - A failing line names its check, a code, what was expected and, for an answer that breaks the
schema, the field. The
X-SR-Request-Idfinds the call in your logs. - The REST binding by example shows the answer each check expects, and Proving it lists every check with what makes it pass.
| Sandbox check | Passes when |
|---|---|
refund_over_refundable |
A real refund one minor unit over the remainder is refused with amount_exceeds_refundable |
refused_key_final |
That key replays refused, get_operation says failed, and no money moved |
refund_executes |
The refund executes once and refunds[] lists it under Chargeless's key |
refund_replay |
The replay returns the original result and refunds nothing more |
refund_conflict |
The key with another amount gets 409 idempotency_conflict, and nothing runs |
cancel_executes, cancel_replay, cancel_conflict |
The same for an immediate cancellation |
cancel_refused_key |
A second cancellation is refused with already_canceled, replays refused and reads failed |
one_effect_per_call |
The refund leaves the subscription alone, and the cancellation leaves the charge alone |
operation_states |
Every key read again keeps its final state, and a key never sent is unknown |
Upload the report
The report file is sealed with a key derived from your sandbox secret, so you can tell later that it is the kit's unmodified output. Upload it in the panel under Settings → Integrations → your connection → Conformance kit: choose the file, tick "This file is the kit's unmodified output for this workspace" and attest it.
- Going live needs a passed report for your transport (REST or MCP) from the last 30 days, and so does a later change of transport.
- Chargeless records the file's SHA-256 (
shasum -a 256 providercheck-report.json), the kit version, the run, the transport, your sandbox's origin and each line's result. It does not keep the file, so keep your copy. - Chargeless cannot verify the seal (it does not keep your sandbox secret): the attestation is your member's statement that the file is the kit's output.
Try it on your machine first
Before your sandbox is ready, run the kit against the complete server on The REST binding by example, as Connect your billing system in an afternoon shows. It passes every line, so you see what a passing run looks like.