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 mandate → let the payer authorise it → wait for the mandate_authorised webhook → charge 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) | |
|---|---|---|
| Currency | AUD | GBP |
system value | PAY_TO | VRP |
| Authorisation | Decoupled (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:
| Header | Used on | Notes |
|---|---|---|
Authorization: Bearer <JWT> | All endpoints | Required |
Idempotency-Key | POST /mandates, POST /mandates/{id}/payments | A 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-Channel | All POST endpoints | Always 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:
| Option | How it works | When to use |
|---|---|---|
| Hosted flow | Volt Checkout presents the authorisation UI; pass X-Volt-Initiation-Channel: hosted | Quickest to integrate |
| API-only | You build your own UI; pass X-Volt-Initiation-Channel: api | Full control over the UX |
Both options use the same POST /mandates endpoint and follow the same mandate lifecycle.
{
"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.
{
"currency": "GBP",
"system": "VRP",
"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"
},
"vrp": {
"type": "COMMERCIAL",
"institutionId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"limits": {
"periodic": {
"period": "MONTH",
"maxAmount": 50000
},
"payment": {
"maxAmount": 10000
}
}
},
"device": {
"ip": "203.0.113.10"
},
"communication": {
"notifications": {
"url": "https://mysite.com/webhooks/mandates"
},
"return": {
"urls": {
"unified": { "url": "https://mysite.com/mandate/result" }
}
}
}
}Response — 201 Created
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"system": "PAY_TO",
"currency": "AUD",
"status": {
"status": "NEW",
"details": null
},
...
"mandateSetupFlow": {
"status": "PROCESSING",
"details": {
"reason": "AWAITING_DECOUPLED_AUTHORISATION"
}
}
}{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"system": "VRP",
"currency": "GBP",
"status": {
"status": "NEW",
"details": null
},
...
"mandateSetupFlow": {
"status": "PROCESSING",
"details": {
"reason": "AWAITING_USER_REDIRECT",
"redirect": {
"url": "https://short.vo.lt/authorisation?code=xyz",
"redirectUrl": "https://volt.io/authorisation?code=xyz"
}
}
}
}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 receiveAWAITING_USER_REDIRECTwith 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): passpayTo.accountIdentifiersyourself and the flow goes straight toAWAITING_DECOUPLED_AUTHORISATION.
cVRP authorisation is redirect-based — the payer is redirected to their bank to approve the mandate.
- Hosted (
X-Volt-Initiation-Channel: hosted): redirect the payer to the returned Volt Checkout URL; bank selection and the redirect to the bank happen there. - API-only (
X-Volt-Initiation-Channel: api): passvrp.institutionId(the payer's bank) and redirect the payer to the returned authorisation URL.
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.
{
"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
GET /mandates/{id} returns the current mandate state, including status, mandateSetupFlow, and the configured limits.
Use it for reconciliation, or to recover state if a webhook was missed.
PATCH /mandates/{id} is used to:
- submit
requiredInputvalues during the setup flow (WAITING_FOR_INPUT), - update system-related fields (
payTo.accountIdentifiers,vrp.institutionId) on a mandate that has not yet been authorised.
For changes to an already-authorised PayTo mandate, use instead.
POST /mandates/{id}/suspend pauses an ACTIVE mandate.
While suspended, recurring payments cannot be initiated.
POST /mandates/{id}/release returns a SUSPENDED mandate to ACTIVE.
Only the party that suspended the mandate may release it.
POST /mandates/{id}/cancel permanently terminates an ACTIVE or SUSPENDED mandate.
No further payments can be initiated after cancellation.
For a mandate that has not been authorised yet, use DELETE /mandates/{id} (recall) instead.
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:
| Status | Meaning |
|---|---|
NEW | Mandate created; awaiting payer action |
AWAITING_AUTHORISATION | Proposed to the payer; waiting for approval |
ACTIVE | Mandate authorised and ready for payments |
SUSPENDED | Temporarily paused |
AUTHORISATION_ABORTED | Payer did not complete the authorisation |
AUTHORISATION_TERMINATED | Authorisation process terminated before completion |
FAILED | Mandate is terminated or processing failed — check status.details |
status.details:
| Details | Meaning |
|---|---|
ABANDONED_BY_PAYER | Payer abandoned the authorisation |
REFUSED_BY_PAYER | Payer explicitly refused the mandate |
CANCELLED_BY_PAYER | Payer cancelled the mandate (e.g. in their banking app) |
CANCELLED_BY_CUSTOMER | You cancelled the mandate |
REVOKED_BY_PAYER | Payer revoked the mandate |
RECALLED | You recalled the mandate before authorisation |
EXPIRED | Mandate reached the end of its validity.to |
CONSUMED | ONE_OFF mandate was used by its first payment |
SUSPENDED_BY_PAYER | Payer suspended the mandate |
SUSPENDED_BY_CUSTOMER | You suspended the mandate |
PAYER_ACCOUNT_VERIFICATION | Payer's account verification in progress |
PAYER_ACCOUNT_VERIFICATION_FAILED | Payer's account verification failed |
INTERNAL_ERROR | Internal Volt error |
EXTERNAL_ERROR | Error 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
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | Volt/2.0 |
X-Volt-Type | The event type — e.g. mandate_authorised (see tables below) |
X-Volt-Timed | Timestamp of notification generation (YYYYMMDDhhmmss) |
X-Volt-Signed | Notification 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-Type | What happened | What to do |
|---|---|---|
mandate_requested | Mandate proposed to the payer | Wait |
mandate_authorised | Payer authorised — mandate is ACTIVE | Start initiating payments |
mandate_declined | Payer declined the mandate | Offer the payer another payment method |
mandate_authorisation_abandoned | Authorisation timed out | Offer to retry with a new mandate |
mandate_suspended | Mandate was suspended | Pause billing; check status.details for who suspended it |
mandate_released | Suspended mandate back to ACTIVE | Resume billing |
mandate_cancelled | Mandate was cancelled | Stop billing; create a new mandate if the relationship continues |
mandate_expired | Mandate reached end of validity | Create a new mandate to continue billing |
mandate_consumed | ONE_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_failed | Mandate processing failed | Check 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
- — complete field list, types, validation rules, and response schemas
- — error codes, validation errors, and troubleshooting
How is this guide?
Last updated on