Loading...

Integration guide

Set up mandates, initiate recurring payments, and manage the mandate lifecycle


Beta

The Mandates API is currently in beta. Request/response shapes might be subject to change. Please, reach out to your Volt contact before integrating.

Overview

The Mandate Management API lets you set up a mandate (a standing authorisation by the payer for future debits), initiate recurring payments against an active mandate, and cancel it when the customer relationship ends.

The whole integration is four steps: create the mandatelet the payer authorise itwait for the mandate_authorised webhookcharge whenever billing is due. The sections below walk through each step, followed by mandate management (suspend, release, cancel), the lifecycle, and webhooks.

PayTo (Australia)cVRP (United Kingdom)
CurrencyAUDGBP
system valuePAY_TOVRP
AuthorisationDecoupled (banking app)Bank redirect
Amendments
Suspend / Release

Amendments are not currently available for cVRP mandates via Volt.

This guide shows example payloads and explains the behaviour around them. For the complete field list, types, and validation rules, see the .

Prerequisites

Authenticate with Volt's API before calling any mandate endpoints. See the .

Request headers used across mandate endpoints:

HeaderUsed onNotes
Authorization: Bearer <JWT>All endpointsRequired
Idempotency-KeyPOST /mandates, POST /mandates/{id}/paymentsA unique key (UUID recommended, max 64 chars) that makes retries safe — resending the same request with the same key will not create a duplicate. Reusing a key with a different payload returns 409 Conflict.
X-Volt-Initiation-ChannelAll POST endpointsAlways required. hosted when using Volt Checkout, api when building your own UI (see below)

Create a mandate

Volt supports two ways to present the mandate authorisation to the payer:

OptionHow it worksWhen to use
Hosted flowVolt Checkout presents the authorisation UI; pass X-Volt-Initiation-Channel: hostedQuickest to integrate
API-onlyYou build your own UI; pass X-Volt-Initiation-Channel: apiFull control over the UX

Both options use the same POST /mandates endpoint and follow the same mandate lifecycle.

POST /mandates — PayTo example:
{
  "currency": "AUD",
  "system": "PAY_TO",
  "mode": "RECURRING",
  "purpose": "RETAIL",
  "payer": {
    "reference": "payer-ref-001",
    "email": "jane.smith@example.com"
  },
  "internalReference": "MY_INTERNAL_REF",
  "validity": {
    "to": "2027-07-01T00:00:00+00:00"
  },
  "payTo": {
    "accountIdentifiers": {
      "accountName": "Jane Smith",
      "payId": "jane.smith@example.com",
      "payIdType": "EMAIL"
    },
    "limits": {
      "periodic": {
        "period": "MONTH",
        "maxPaymentsCount": 3
      },
      "payment": {
        "maxAmount": 10000
      }
    },
    "description": "Monthly subscription"
  },
  "device": {
    "ip": "203.0.113.10"
  },
  "communication": {
    "notifications": {
      "url": "https://mysite.com/webhooks/mandates"
    },
    "return": {
      "urls": {
        "unified": {
            "url": "https://mysite.com/mandate/result"
        }
      }
    }
  }
}

No periodic amount cap on PayTo

PayTo limits are per payment (amount or maxAmount) and payments per period (maxPaymentsCount).

The effective per-period spend cap is payment limit × maxPaymentsCount. For example — period: MONTH, maxPaymentsCount: 3, maxAmount: 10000 allows at most 300 AUD per month.

Response — 201 Created

API-only — the payer approves in their banking app:
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "system": "PAY_TO",
  "currency": "AUD",
  "status": {
    "status": "NEW",
    "details": null
  },
  ...
  "mandateSetupFlow": {
    "status": "PROCESSING",
    "details": {
      "reason": "AWAITING_DECOUPLED_AUTHORISATION"
    }
  }
}

The full response use the same object that GET /mandates/{id} returns. Note that status is an object: status.status is the lifecycle state and status.details explains it (see Mandate lifecycle). The mandateSetupFlow object drives the authorisation process — handle it as described next.

Handle the setup flow

mandateSetupFlow works exactly like paymentInitiationFlow in the Global Payments API — the same status values, details.reason values, redirect object, and requiredInput mechanics. See for the full state machine.

Payer authorisation differs per scheme:

PayTo authorisation is decoupled — the payer approves the mandate in their banking app rather than being redirected to their bank.

  • Hosted (X-Volt-Initiation-Channel: hosted): you receive AWAITING_USER_REDIRECT with a Volt Checkout URL where the payer provides their account details; the mandate is then proposed for approval in their banking app.
  • API-only (X-Volt-Initiation-Channel: api): pass payTo.accountIdentifiers yourself and the flow goes straight to AWAITING_DECOUPLED_AUTHORISATION.

Recalling an unauthorised mandate

To withdraw a mandate that has not yet been authorised, call DELETE /mandates/{id}. Only valid before the mandate reaches ACTIVE; afterwards use Cancel.

Wait for the authorisation webhook

Volt sends a webhook to communication.notifications.url whenever the mandate status changes — the event type is in the X-Volt-Type header (see Webhooks for the delivery format and all events):

POST /webhooks/mandates HTTP/1.1
Content-Type: application/json
User-Agent: Volt/2.0
X-Volt-Type: mandate_authorised
X-Volt-Timed: 20260612123456
X-Volt-Signed: eda5e46baa6a676851975365e12b4ae61ee48442c0cbb8d0e3c3cfd47c3e1085

{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "system": "PAY_TO",
  "currency": "AUD",
  "status": {
    "status": "ACTIVE",
    "details": null
  },
  ...
}

On mandate_authorised, the mandate is ACTIVE and you can start initiating payments.

You can also poll GET /mandates/{id} at any time — use it for reconciliation or to recover state if a webhook is missed.

Initiate a recurring payment

Once the mandate is ACTIVE, debit the payer by calling POST /mandates/{id}/payments.

POST /mandates/{id}/payments:
{
  "currency": "AUD",
  "amount": 10000,
  "paymentReference": "INV2026001",
  "internalReference": "H35X48Y4FWOD6G3S",
  "communication": {
    "notifications": {
      "url": "https://mysite.com/webhooks/payments"
    }
  }
}

The request shape is identical for PayTo and cVRP — see the for the request fields. Payment amount and frequency must respect the mandate's limits, and remember the Idempotency-Key header so a retried request cannot charge the payer twice.

The response is a standard payment object — status can be fetched with GET /payments/{id}.

You can configure payment notifications in the Fuzebox or pass them directly in the request.

Manage an active mandate

Mandate lifecycle

The mandate status is an object with two parts: status.status (the lifecycle state) and status.details (why it is in that state).

status.status:

StatusMeaning
NEWMandate created; awaiting payer action
AWAITING_AUTHORISATIONProposed to the payer; waiting for approval
ACTIVEMandate authorised and ready for payments
SUSPENDEDTemporarily paused
AUTHORISATION_ABORTEDPayer did not complete the authorisation
AUTHORISATION_TERMINATEDAuthorisation process terminated before completion
FAILEDMandate is terminated or processing failed — check status.details

status.details:

DetailsMeaning
ABANDONED_BY_PAYERPayer abandoned the authorisation
REFUSED_BY_PAYERPayer explicitly refused the mandate
CANCELLED_BY_PAYERPayer cancelled the mandate (e.g. in their banking app)
CANCELLED_BY_CUSTOMERYou cancelled the mandate
REVOKED_BY_PAYERPayer revoked the mandate
RECALLEDYou recalled the mandate before authorisation
EXPIREDMandate reached the end of its validity.to
CONSUMEDONE_OFF mandate was used by its first payment
SUSPENDED_BY_PAYERPayer suspended the mandate
SUSPENDED_BY_CUSTOMERYou suspended the mandate
PAYER_ACCOUNT_VERIFICATIONPayer's account verification in progress
PAYER_ACCOUNT_VERIFICATION_FAILEDPayer's account verification failed
INTERNAL_ERRORInternal Volt error
EXTERNAL_ERRORError at the provider or bank

Payer-initiated changes

Payers can cancel a mandate at any time in their banking app (and, for PayTo, also pause it). Volt is notified by the scheme, updates the mandate status accordingly (status.details tells you who initiated the change), and notifies you via webhook (e.g. mandate_cancelled).

Consumer switches bank

Mandates are linked to a specific bank account. If a payer switches banks, a new mandate must be created.

Amendments (PayTo only)

To change mandate details or limits on an ACTIVE or SUSPENDED PayTo mandate, see the .

Amendments are not currently supported for cVRP (UK) mandates. To change limits or details, cancel the existing mandate and create a new one.

Webhooks

Volt delivers webhooks to the communication.notifications.url you provided when creating the mandate.

Listen to both mandate webhooks and payment webhooks. Mandate webhooks track the lifecycle of the mandate itself; payment webhooks report the outcome of each individual payment.

Delivery format

HeaderValue
Content-Typeapplication/json
User-AgentVolt/2.0
X-Volt-TypeThe event type — e.g. mandate_authorised (see tables below)
X-Volt-TimedTimestamp of notification generation (YYYYMMDDhhmmss)
X-Volt-SignedNotification signature — same verification as the rest of the Volt API; see the

The body is the full mandate object (for mandate events) or the full amendment object (for amendment events) — read the event type from the X-Volt-Type header, not the body. See the example delivery above.

Mandate events

X-Volt-TypeWhat happenedWhat to do
mandate_requestedMandate proposed to the payerWait
mandate_authorisedPayer authorised — mandate is ACTIVEStart initiating payments
mandate_declinedPayer declined the mandateOffer the payer another payment method
mandate_authorisation_abandonedAuthorisation timed outOffer to retry with a new mandate
mandate_suspendedMandate was suspendedPause billing; check status.details for who suspended it
mandate_releasedSuspended mandate back to ACTIVEResume billing
mandate_cancelledMandate was cancelledStop billing; create a new mandate if the relationship continues
mandate_expiredMandate reached end of validityCreate a new mandate to continue billing
mandate_consumedONE_OFF mandate used by its first payment (such mandates are created implicitly for one-off PayTo payments)Nothing — this is the expected end state for ONE_OFF
mandate_failedMandate processing failedCheck status.details; contact Volt if unexpected

Payment outcome webhooks (e.g. payment.completed, payment.failed) are sent separately for each POST /mandates/{id}/payments call — configure these as described in the .

For amendment events (mandate_amendment_*), see the .

Reference

How is this guide?

Last updated on

On this page