Build a provider endpoint
Let Voidhash import products and history from a payment source, and cancel or refund there.
A provider endpoint is one POST route on your server that answers Voidhash's signed requests
for a payment source. It's optional: reporting payments works without it.
With it, Voidhash can:
- list your products in Import products;
- Import history: record subscriptions, payments and refunds from before you connected, skipping any ID already recorded;
- ask for a subscription's state when its period ends without a renewal or final state;
- cancel a subscription or refund a payment at your provider.
Build it with the provider handler of the Node.js, PHP, Go or Rust library. The handler verifies signatures, checks inputs and results, and answers in the format below. Implement only the operations your provider supports.
Connect the endpoint
- On the source's Provider endpoint tab, enter the Endpoint URL and select Save changes.
Voidhash creates a signing secret (
vhps_…) and shows it once. - Set the secret on your server, for example as
VOIDHASH_PROVIDER_SECRET. - Select Test connection. Voidhash sends a signed
describerequest and lists the operations your endpoint implements.
The endpoint must be a public HTTPS address; redirects aren't followed. Voidhash waits up to 10
seconds for an answer and retries an unavailable read twice, but sends a cancellation or refund
only once. Rotate secret replaces the secret, and requests signed with the old one stop
verifying, so update your server right away. Disconnect stops Voidhash from calling it.
Add a test endpoint
Each request carries an environment. Add a Test endpoint, such as your staging server, to
receive test requests with its own signing secret:
| Request | Goes to |
|---|---|
| Test: history, subscription checks, cancel, refund | The test endpoint, or the endpoint if there is none. |
| Production: history, subscription checks, cancel, refund | The endpoint only. |
| Product import | Both, each in its own environment, listed together. |
With only a test endpoint, production requests fail with payment_source_endpoint_not_configured
until you add the endpoint. Use the request's environment to pick your provider's live or test
account; a deployment that serves one environment can answer the other with invalid_request.
Requests
Voidhash sends POST with a JSON body { protocolVersion: 1, id, source, environment, operation, input }, where id is unique per request and source is the source's slug. It signs the request
like a webhook:
x-voidhash-timestamp: Unix seconds.x-voidhash-signature:v1=and the lowercase hex HMAC-SHA256 of{timestamp}.{raw body}, keyed with the signing secret.
Refuse a timestamp more than 5 minutes from your clock, so keep it in sync. The signature covers the exact bytes, so mount the route before any body parser.
Operations
| Operation | Input | Result |
|---|---|---|
describe | {} | { protocolVersion, source, operations }. The libraries answer it. |
catalog.list | cursor | items of { key, name, kind }, kind being subscription, one_time or consumable; nextCursor. |
history.list | kind, cursor, before | items of the asked kind, created before before; nextCursor. |
subscription.get | id | { subscription }, or null when your provider doesn't know it. |
subscription.cancel | id, atPeriodEnd | { subscription }: its state after canceling, at the period's end or at once. |
transaction.refund | transactionId, amount (null for everything left), reason | { refund }: a transaction with type: "refund". |
Voidhash records what subscription.cancel and transaction.refund return as if you had
reported it.
History
history.list asks for kind: "subscriptions" first, then "transactions", paging each with
cursor until nextCursor is null. before is when the import started; later records reach
Voidhash through your reports. A page holds at most 1,000 items, and record IDs are at most 200
characters.
Items have the fields you report plus id, with two differences: a subscription says willRenew
where reports say isAutoRenewing, and items carry no environment, since they belong to the
request's.
Cancel and refund from Voidhash
With an endpoint that implements subscription.cancel and transaction.refund, cancel and refund at your provider from Studio, the CLI or
the API. Voidhash calls your endpoint and records the result. This differs from reporting a
refund: reporting tells Voidhash about a refund your provider made, while these ask your provider to
make one. If your provider also sends you the change as an event and you report it, nothing is
recorded twice.
- Cancel a subscription: on the person's page, choose Cancel, then Cancel at period end
or Cancel now; or
POST …/subscriptions/{id}/cancelwith optionalisImmediate: true; orvoidhash provider cancel-subscription. - Refund a payment: choose Refund on the purchase; or
POST …/transactions/{id}/refundwith an optionalamountin minor units (default: everything left) andreason; orvoidhash provider refund.
When the endpoint can't act, Voidhash records nothing and returns:
| Status | Code | Meaning |
|---|---|---|
409 | payment_source_endpoint_not_configured | No endpoint for the record's environment. A test endpoint never receives production records. |
409 | payment_source_endpoint_unsupported | The endpoint doesn't implement the operation. |
409 | payment_source_endpoint_rejected | Your endpoint answered not_found or invalid_request; message says why. |
503 | payment_source_endpoint_unavailable | No answer in time, a 5xx, unavailable or provider_error. |
503 | payment_source_endpoint_invalid_response | An answer outside the protocol, or invalid_result. |
404 | transaction_not_found | The charge to refund isn't recorded. |
A 409 needs a change first. Before retrying a 503, check at your provider whether the action
went through: one that timed out may still have happened.
Errors
Answer 200 with { id, result }, or with { id, error: { code, message } } and the code's status:
| Code | Status | Meaning |
|---|---|---|
not_found | 404 | Nothing at your provider matches the input. |
invalid_request | 400 | The input makes no sense to you, such as a refund for too much. |
unavailable | 503 | Your provider is down or rate limiting you. Voidhash retries reads. |
provider_error | 500 | Anything else your code throws. Production answers hide its message. |
invalid_signature | 401 | Sent by the handler: a missing, wrong or stale signature. |
unsupported_operation | 404 | Sent by the handler: an operation you didn't implement. |
invalid_result | 500 | Sent by the handler: your result breaks the protocol; the message names the field. |
Error messages reach Studio and the API, so keep them free of secrets. Voidhash records nothing from
an invalid_result, but a cancellation or refund may still have happened at your provider; your
usual reports record it.
Test an endpoint
Each library has a test helper that signs a request with your secret and runs it through your
handler, so you can unit test operations without Voidhash. Test requests show your own
provider_error messages. Pass every input field, with null where it has no value, as Voidhash
does.
Next steps
- Build the handler with Node.js, PHP, Go or Rust
- Report subscriptions
- Test and staging
Something wrong or missing on this page? Tell us by email or on Discord.