VoidhashDocs
DevelopersHelp centerGuidesAPI reference
Dashboard
Get started
Introduction
Quickstart
How Voidhash works
Glossary
Versions and releases
Concepts
Teams, projects and environments
API keys
Products and perks
Offers
Subscription lifecycle
People and identity
Events
Features
Paywalls in your app
Feature flags and A/B tests
Paywall components
Push notifications
Ad attribution in your app
Client SDKs
React Native
Swift
Kotlin
Web
Your backend
Node.js
PHP
Go
Rust
Other languages
Payment sources
Forward store notifications
Developer tools
CLI
MCP server
Query analytics with VoidQL
Payment sources
Transaction fields
Report subscriptions
Build a provider endpoint

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

  1. 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.
  2. Set the secret on your server, for example as VOIDHASH_PROVIDER_SECRET.
  3. Select Test connection. Voidhash sends a signed describe request 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:

RequestGoes to
Test: history, subscription checks, cancel, refundThe test endpoint, or the endpoint if there is none.
Production: history, subscription checks, cancel, refundThe endpoint only.
Product importBoth, 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

OperationInputResult
describe{}{ protocolVersion, source, operations }. The libraries answer it.
catalog.listcursoritems of { key, name, kind }, kind being subscription, one_time or consumable; nextCursor.
history.listkind, cursor, beforeitems of the asked kind, created before before; nextCursor.
subscription.getid{ subscription }, or null when your provider doesn't know it.
subscription.cancelid, atPeriodEnd{ subscription }: its state after canceling, at the period's end or at once.
transaction.refundtransactionId, 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}/cancel with optional isImmediate: true; or voidhash provider cancel-subscription.
  • Refund a payment: choose Refund on the purchase; or POST …/transactions/{id}/refund with an optional amount in minor units (default: everything left) and reason; or voidhash provider refund.

When the endpoint can't act, Voidhash records nothing and returns:

StatusCodeMeaning
409payment_source_endpoint_not_configuredNo endpoint for the record's environment. A test endpoint never receives production records.
409payment_source_endpoint_unsupportedThe endpoint doesn't implement the operation.
409payment_source_endpoint_rejectedYour endpoint answered not_found or invalid_request; message says why.
503payment_source_endpoint_unavailableNo answer in time, a 5xx, unavailable or provider_error.
503payment_source_endpoint_invalid_responseAn answer outside the protocol, or invalid_result.
404transaction_not_foundThe 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:

CodeStatusMeaning
not_found404Nothing at your provider matches the input.
invalid_request400The input makes no sense to you, such as a refund for too much.
unavailable503Your provider is down or rate limiting you. Voidhash retries reads.
provider_error500Anything else your code throws. Production answers hide its message.
invalid_signature401Sent by the handler: a missing, wrong or stale signature.
unsupported_operation404Sent by the handler: an operation you didn't implement.
invalid_result500Sent 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.

PreviousReport subscriptionsNextForward store notifications

On this page

Connect the endpointAdd a test endpointRequestsOperationsHistoryCancel and refund from VoidhashErrorsTest an endpointNext steps