Customer billing

Charge your own customers for the verification and KYB work you run on them, instead of paying for it on your partner account.

What this is

By default every verification and KYB lookup you trigger is billed to your partner account. To bill one of your own customers instead, create a contact and have it save a card.

This is not an ATO payment. Customer billing charges your customer for work you ran on them. To move money to the ATO, see ATO payments.

1. Create the contact

POST /v1/contacts
X-Partner-Id: YOUR_PARTNER_ID
Content-Type: application/json

{
  "name": "Acme Pty Ltd",
  "externalId": "your-key-42",
  "contactEmail": "accounts@acme.example"
}

name is the only required field. Send the contact's own business details, not their end users' personal information. externalId is your opaque key, immutable after create. A duplicate returns 409 CONTACT_EXTERNAL_ID_EXISTS.

The contact starts in billingStatus: NO_PAYMENT_METHOD.

POST /v1/contacts/{id}/card-link
X-Partner-Id: YOUR_PARTNER_ID
Content-Type: application/json

{ "redirectUrl": "https://app.example.com/card-done?contact={contactId}" }

This mints a single-use guest URL. Send it to the contact through your own channel. On the hosted page the contact accepts the merchant-initiated billing authorisation and saves a card.

The url is returned once and is never re-issued. Capture it from this response.

Optionally pass a redirectUrl in the body to send the contact back to your platform when they finish. They are redirected to it with ?status=success or ?status=removed appended. Include a {contactId} placeholder to get the contact id back:

https://app.example.com/card-done?contact={contactId}

3. Wait for the card

Wait for the contact.card.saved webhook, or poll GET /v1/contacts/{id} until billingStatus: ACTIVE.

Card management is guest-only. The contact saves, replaces and removes its card exclusively through the minted link. There are no partner payment-method endpoints.

4. Bill the work

Pass contactId on POST /v1/verifications or POST /v1/business-lookups. Admiin then runs the contact billing gate.

Charges settle against the contact's card once the unpaid balance crosses the $50 threshold. Any smaller non-zero balance is collected at the start of each month.

Each settlement charges the net batch total plus a 1.9% card processing fee. The contact.billing.* webhooks report totalAmountCents (net), feeAmountCents and grossAmountCents, which is what hit the card.

When the gate blocks

A contact-billed call returns one of these when the gate blocks it. No verification or lookup is performed, and no charge is written.

code Meaning
PAYMENT_METHOD_REQUIRED The contact has no saved card yet.
CONTACT_BILLING_BLOCKED Billing is blocked on this contact.
CONTACT_NOT_FOUND No such contact for this partner.
CONTACT_INVALID The contact is not in a billable state.

Reconciling charges

GET /v1/charges?contactId={id}
X-Partner-Id: YOUR_PARTNER_ID

Lists the individual charges behind a contact's settlements. Narrow it with billingRunId, paymentStatus or a fromDate and toDate range.

For a spreadsheet rather than JSON:

POST /v1/charges/export
X-Partner-Id: YOUR_PARTNER_ID

Synchronous. The CSV comes back in the 200 body as text/csv. There is no job to poll.

Pricing

GET /v1/pricing
X-Partner-Id: YOUR_PARTNER_ID

Returns your fee catalogue, with any partner-specific overrides layered over the defaults. It is a catalogue, not a rate card. Check billable on each line before you publish it to your own customers.

Archiving a contact

POST /v1/contacts/{id}/archive
X-Partner-Id: YOUR_PARTNER_ID

Soft-archives the contact. It is refused with 409 CONTACT_HAS_UNPAID_BALANCE while money is still owed, unless you pass ?force=true.

Billing webhooks

Track the lifecycle with the contact.card.* and contact.billing.* events. See Webhooks for signature validation, and the contact billing events in the API reference.