JavaScript is required to view this page
Fixed Recurring Payments

Fixed Recurring Payments

Fixed Recurring Payments automate scheduled payments against an authorised enduring consent. Once set up, payments execute automatically on a recurring basis without further merchant or customer intervention.

When to Use Fixed Recurring

Fixed Recurring is ideal when:

  • The payment amount stays the same each period (e.g., $29.99/month)
  • You want BlinkPay to handle payment execution automatically
  • You don't need custom timing logic

Common use cases: Fixed-fee subscriptions, charity donations, membership dues, loan repayments, gym memberships.

For variable amounts or custom timing, use Variable Recurring Payments instead.

FRP and FPE

Two concepts to keep straight:

  • Fixed Recurring Payment (FRP) — the schedule itself. One per recurring arrangement, tied to one authorised enduring consent. It holds the amount, PCR, start date, next payment date, retry strategy and status. It has no frequency or end date of its own: the frequency is the consent's period, and the schedule ends when it is cancelled or when the consent is revoked or reaches its expiry_timestamp. Think of it as similar to a direct debit mandate.
  • Fixed Payment Execution (FPE) — one occurrence of an FRP firing on a specific date. Many FPEs per FRP. Created by the scheduler ahead of its scheduled date, and links to the actual payment row created at the bank when it executes. Think of it as one individual debit under that mandate.

Each FPE has its own status, retry behaviour, and webhook events, so you get granular visibility into every individual debit. Throughout this guide, "schedule" or "FRP" refers to the recurring rule and "FPE" refers to an individual scheduled execution.

How Fixed Recurring Payments Work

One-Time Setup:

  1. Create an Enduring Consent and have the customer authorise it
  2. Create a Fixed Recurring Payment schedule linked to that consent

Automated Payments: BlinkPay automatically initiates payments at 9am NZ time on each scheduled date. Payments continue until the schedule is cancelled or the underlying consent is revoked or expires.

Prerequisites

Before creating a Fixed Recurring Payment schedule, you must first have an authorised enduring consent. See Recurring Payments for how to create and authorise an enduring consent.

Requirements:

  • An enduring consent in Authorised status
  • Payment amount must not exceed the consent's maximum_amount_payment or maximum_amount_period
  • start_date must be today or in the future (NZ timezone); omit it to default to the later of today and the consent's activation date
  • For daily consents, start_date cannot be today if the request is submitted after 21:45 NZ — set it to tomorrow instead
  • Only one active schedule allowed per consent

The consent's from_timestamp and expiry_timestamp are converted to NZ time before their dates are compared with start_date, so a timestamp sent in another offset may fall on a different NZ date.

One Schedule Per Consent

Each enduring consent can have only one active Fixed Recurring Payment schedule at a time. To change the schedule, cancel the existing one and create a new schedule.

API Endpoints

MethodPathDescription
POST/payments/v1/fixed-recurring-paymentsCreate schedule
GET/payments/v1/fixed-recurring-payments/{fixed_recurring_payment_id}Get schedule
DELETE/payments/v1/fixed-recurring-payments/{fixed_recurring_payment_id}Cancel schedule

Creating a Schedule

Request:

POST /payments/v1/fixed-recurring-payments HTTP/1.1
Host: sandbox.debit.blinkpay.co.nz
Authorization: Bearer {access_token}
Content-Type: application/json
idempotency-key: b5d9f3a7-2c6e-4b1d-8f4a-0e7c3a9d5b2f

{
  "consent_id": "8f7e6d5c-4b3a-2c1b-0a09-8f7e6d5c4b3a",
  "start_date": "2026-01-14",
  "amount": {
    "total": "99.00",
    "currency": "NZD"
  },
  "pcr": {
    "particulars": "Subscription",
    "code": "Monthly",
    "reference": "Service"
  },
  "retry_strategy": "same_day"
}

Request Fields

FieldRequiredDescription
consent_idyesAn authorised enduring consent.
amountyesPer-payment amount. Must not exceed the consent's maximum_amount_payment or maximum_amount_period.
pcryesParticulars, Code, and Reference shown on the customer's bank statement. Each field max 12 characters.
start_datenoFirst scheduled payment date (NZ timezone). Must be today or in the future. Defaults to the later of today and the consent's activation date (its from_timestamp, NZ timezone) if omitted. For daily consents, cannot be today when the request is submitted after 21:45 NZ.
retry_strategynonone (default) or same_day — see Retry Strategies.

PCR Field Limits

Each PCR field (particulars, code, reference) has a maximum length of 12 characters. Requests with longer values are rejected with a 400 validation error — values are never truncated.

Response:

{
  "fixed_recurring_payment_id": "9f8e7d6c-5b4a-3c2b-1a09-8f7e6d5c4b3a"
}

Retrying the create request. Send an idempotency-key (a UUID) and reuse it if the request times out: the same key and body return the original fixed_recurring_payment_id. A consent can have only one active schedule (409 BP705).

Surcharging

If BlinkPay has enabled surcharging on enduring consents for your merchant account, instalments are surcharged by the same rules as any other payment on the consent; see Surcharging. The schedule's amount is the amount before the surcharge.

An instalment whose amount equals the consent's maximum_amount_payment is surcharged in full, less a few cents of rounding, unless rates have risen since the consent was created, in which case the surcharge is capped at what the consent allows. Smaller instalments follow the rules on that page.

Checking Schedule Status

Request:

GET /payments/v1/fixed-recurring-payments/{fixed_recurring_payment_id} HTTP/1.1
Host: sandbox.debit.blinkpay.co.nz
Authorization: Bearer {access_token}

Response:

{
  "fixed_recurring_payment_id": "9f8e7d6c-5b4a-3c2b-1a09-8f7e6d5c4b3a",
  "consent_id": "8f7e6d5c-4b3a-2c1b-0a09-8f7e6d5c4b3a",
  "status": "active",
  "start_date": "2025-12-17",
  "next_payment_date": "2026-01-17",
  "amount": {
    "total": "99.00",
    "currency": "NZD"
  },
  "pcr": {
    "particulars": "Subscription",
    "code": "Monthly",
    "reference": "Service"
  },
  "retry_strategy": "same_day",
  "creation_timestamp": "2025-12-17T09:00:00+13:00"
}

Response Fields

FieldDescription
fixed_recurring_payment_idUnique identifier for the schedule.
consent_idThe underlying enduring consent.
statusactive or cancelled.
start_dateThe first scheduled payment date. Taken from the create request; defaults to the later of today and the consent's activation date (NZ timezone) if not supplied.
next_payment_dateNext scheduled payment date.
amountPer-payment amount, as supplied at creation.
pcrParticulars, Code, and Reference shown on the customer's bank statement.
retry_strategynone or same_day — see Retry Strategies.
creation_timestampWhen the schedule was created (ISO 8601).
status_updated_timestampWhen the status was last updated (ISO 8601).

Cancelling a Schedule

Request:

DELETE /payments/v1/fixed-recurring-payments/{fixed_recurring_payment_id} HTTP/1.1
Host: sandbox.debit.blinkpay.co.nz
Authorization: Bearer {access_token}

Response: 204 No Content

Cancellation Does Not Revoke Consent

Cancelling a Fixed Recurring Payment schedule does NOT revoke the underlying enduring consent. The consent remains active and can be used for manual payments or a new schedule.

Manual Payments Alongside a Schedule

You can make your own payments with POST /payments/v1/payments on a consent that has an active schedule, for example to collect a one-off charge. They share the consent with the schedule's instalments, which has three effects:

  • Manual payments and instalments count towards the same maximum_amount_period, so a manual payment can leave too little for the next instalment in that period.
  • Only one payment runs on a consent at a time. A manual payment sent while an instalment is in progress is refused with 409 BP703, which names the payment to poll.
  • At ANZ and Westpac, the next instalment waits until a full period has passed since the latest payment on the consent, including a manual one, so a manual payment can push the next instalment back.

See Recurring Payments for how to make and retry manual payments.

Payment Schedule

Timing

EventTiming
First paymentInitiated at the first scheduler tick at or after 09:00 NZ time on start_date; at some banks, if the consent's from_timestamp is later that day, the first payment waits until the consent is active.
Subsequent paymentsAnchored to the FRP's start_date, advanced by the period count (not chained off the previous FPE). Initiated at the first tick from 09:00 NZ time on the scheduled date, except at banks with their own payment spacing rules — see Bank-specific timing below.
Operating windowTicks every 15 minutes from 09:00 NZ time (Pacific/Auckland) with the last tick at 21:45; the retry window closes at 22:00

The operating window governs when payment attempts and same_day retries start — it does not delay webhook events, which fire whenever an FPE actually resolves, at any hour. If an FPE's scheduled date passes before the scheduler processes it (for example, a schedule created later in the day with today's start_date), it is not skipped and its scheduledDate is not rewritten — it processes at the next scheduler tick. Periods always roll forward.

Daily schedules only: a daily FRP is rejected at creation if its first payment could not run on start_date — either the request is submitted after the day's last scheduler tick (21:45 NZ), or the consent does not become active until after that tick on that date. Set start_date to the following day and retry. Doesn't apply to other periods.

Once an FPE has been submitted to the bank, its outcome is resolved 24/7 — the operating window only governs when first attempts and retries start, not when in-flight payments settle. An FPE submitted at 21:40 that the bank confirms at 23:00 reaches its terminal status that night and fires the corresponding webhook.

Bank-specific timing

ANZ measures a consent's period between payments to the second, so ANZ instalments are initiated at around the same time of day as the previous payment rather than at 09:00. Blink applies the spacing; the scheduled date is unchanged. ASB, BNZ, Kiwibank and Westpac use calendar period boundaries, so their instalments are initiated at the first tick from 09:00.

Period Types

The payment frequency is determined by the underlying enduring consent's period:

PeriodNth date calculation (N = number of periods elapsed)
dailystart_date + N days
weeklystart_date + N weeks
fortnightlystart_date + (N × 2) weeks
monthlystart_date + N months
annualstart_date + N years

Period Alignment

The schedule is anchored to the FRP's start_date. For example, a monthly schedule starting on 14 January fires on 14 January, 14 February, 14 March, and so on.

Month-End Behaviour

For monthly schedules, each scheduled date is calculated from the FRP's start_date anchor, not from the previous scheduled date. When the anchor day doesn't exist in a given month (e.g. the 31st of a 30-day month), the schedule falls back to the last day of that month for that period only and resumes the original anchor day in the next month where it exists.

For example, a schedule starting on 31 January fires on 31 Jan → 28 Feb (or 29 in leap years) → 31 Mar → 30 Apr → 31 May → 30 Jun → 31 Jul. This matches the NZ Open Banking Payment Initiation period definition for monthly anchoring.

Retry Strategies

StrategyBehaviour
noneNo retries — the schedule advances to the next period on failure.
same_dayRetries hourly within the same calendar day, then advances to the next period.

Retry Strategy: none (Default)

On failure the FPE is marked failed, the fixed-recurring-payment-failed webhook fires, and the schedule advances to its next scheduled date. No retries are attempted.

Retry Strategy: same_day

On failure, the FPE is retried roughly hourly through the day, up to 22:00 NZ time. If it still hasn't succeeded by then, the FPE is marked failed and the schedule advances. Retries do not carry over to the next day.

BlinkPay's retries can't double-charge; don't add your own retries of a failed instalment on top; if you want to collect a missed amount yourself, make a manual payment and follow the retry rules for manual payments.

Some bank rejections cannot be cleared by retrying within the same period and are not retried — the FPE is marked failed immediately and the schedule advances to its next scheduled date. This covers cases like the consent's per-payment or per-period cap being exceeded and the consent's frequency or total-count limit being reached. If the bank's rejection shows the consent itself has been revoked or is no longer authorised, the schedule is cancelled instead — the fixed-recurring-payment-cancelled webhook fires rather than failed (see Auto-Cancellation). Transient infrastructure failures (bank 5xx, network errors) still follow the hourly retry budget.

Schedule Status

StatusDescription
activeSchedule is running and will execute payments
cancelledSchedule has been cancelled (manually or automatically)

Auto-Cancellation

Schedules are automatically cancelled when the underlying consent becomes unusable. The timing depends on how the consent ended:

  • Merchant calls DELETE /enduring-consents/{id} — the FRP is cancelled synchronously as part of the revoke transaction, and the fixed-recurring-payment-cancelled webhook fires immediately.
  • Customer revokes from their bank, or the consent reaches its expiry_timestamp — the schedule is cancelled no later than its next payment date, and the fixed-recurring-payment-cancelled webhook fires then. This webhook is the only push notice of a bank-side revocation.

FPE Tracking

Each FPE is recorded as a standard payment. Query the parent enduring consent to retrieve every FPE that has run against it:

Request:

GET /payments/v1/enduring-consents/{consent_id} HTTP/1.1
Host: sandbox.debit.blinkpay.co.nz
Authorization: Bearer {access_token}

The response includes a payments array containing every FPE made against the consent, each with its own status, timestamp, and amount.

Webhooks

Rather than polling each FPE for an outcome, register a webhook subscription and Blink will deliver a signed event each time an FPE completes, fails, or is cancelled. Webhooks are the recommended pattern for FRP — payment timing is determined by Blink rather than the merchant, so push notification fits the workflow.

Scope: Webhooks fire for FRP events only. PayNow, quick payments, and one-off enduring payments use polling — see the payment lifecycle for those. Decoupled-flow consent authorisation uses a separate unsigned callback mechanism.

Subscription management

Webhook subscriptions are managed via the API. Each subscription is merchant-wide and can be filtered to specific event types. You can register multiple subscriptions (for example, to route different events to different URLs).

MethodPathDescription
POST/payments/v1/subscriptionsCreate a subscription
GET/payments/v1/subscriptionsList subscriptions
DELETE/payments/v1/subscriptions/{id}Delete a subscription

Create a subscription:

Request:

POST /payments/v1/subscriptions HTTP/1.1
Host: sandbox.debit.blinkpay.co.nz
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "callback_url": "https://yourapp.example.com/webhooks/blinkpay",
  "event_types": [
    "urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-completed",
    "urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-failed",
    "urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-cancelled"
  ]
}

Response:

{
  "subscription_id": "9f8e7d6c-5b4a-3c2b-1a09-8f7e6d5c4b3a",
  "callback_url": "https://yourapp.example.com/webhooks/blinkpay",
  "event_types": [
    "urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-completed",
    "urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-failed",
    "urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-cancelled"
  ],
  "secret": "whsec_a1b2c3d4e5f6",
  "creation_timestamp": "2026-01-14T09:00:00+13:00"
}
FieldDescription
subscription_idUnique identifier for the subscription.
secretSigning secret (whsec_… prefix). Store immediately — returned only on create.
callback_urlHTTPS URL BlinkPay POSTs event payloads to.
event_typesThe event types this subscription receives.
creation_timestampWhen the subscription was created (ISO 8601).

Sandbox and production are separate environments with separate subscriptions and separate secrets. There is no pause or disable; to stop deliveries, delete the subscription.

Events

All three events share a single payload schema. The event type identifies which lifecycle moment fired.

Event typeFires when
urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-completedAn FPE has been accepted by the bank.
urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-failedAn FPE has failed terminally and the schedule has advanced to the next period.
urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-cancelledThe schedule has been cancelled — either via the API or because the underlying consent was revoked or expired.

A failed event means the bank declined the instalment or did not respond; the API does not say which. The payment carries no rejection reason, so GET /payments/v1/payments/{payment_id} shows only that it is Rejected, and a failed event without a payment_id means no payment record was created.

Mental model: failed means "this debit didn't go through, but the schedule is still active and will try again next period" — typically prompt the customer to update payment details, but don't terminate their subscription. cancelled means "the schedule itself is gone, no more debits will happen" — typically revert the customer to a non-active state and prompt re-authorisation if you want them back.

Payload

FieldTypeNotes
event_typestring (URN)One of the three event types above.
event_idUUIDStable across delivery retries — use this to dedupe.
timestampISO 8601 with offsetWhen the event was generated.
frp_idUUIDThe schedule the event relates to.
consent_idUUIDThe underlying enduring consent.
payment_idUUID, conditionalThe specific FPE. Always present on completed. Present on failed when a payment record was created (i.e. the failure occurred after the bank submission was attempted). Omitted on cancelled — schedule cancellation is not tied to a specific payment.

Example:

{
  "consent_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "event_id": "12345678-1234-1234-1234-123456789012",
  "event_type": "urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-completed",
  "frp_id": "11111111-2222-2222-2222-333333333333",
  "payment_id": "87654321-4321-4321-4321-210987654321",
  "timestamp": "2025-01-01T12:00:00+13:00"
}

Check for the key's presence (not its value) when reading payment_id. Use frp_id and consent_id as your primary correlation keys — both are present on every event.

Payloads contain identifiers only — no customer name, account number, amount, or PCR fields. To retrieve those, call GET /payments/v1/payments/{payment_id} or the consent endpoint with your normal API credentials.

Delivery headers

Each delivery includes:

  • X-Signature — Unix timestamp and HMAC-SHA256 signature, separated by a comma. See Signature verification below.
  • X-Idempotency-Key — UUID that is stable across delivery retries of the same event. Use this (or event_id from the body) to dedupe.
X-Signature: t=1739545800,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-Idempotency-Key: 7b9c8e2a-4d1f-4a3b-9e5c-6f8a2b4d3e1c
Content-Type: application/json

Signature verification

To verify:

  1. Parse t and v1 from the header.
  2. Build the signed string by concatenating the timestamp, a literal dot, and the raw request body bytes — for example 1739545800.{...body...}. Do not re-serialise the JSON; whitespace and key order matter.
  3. Compute HMAC-SHA256(signed_string, your_signing_secret) and hex-encode the result in lowercase. The signed string and the secret are both treated as UTF-8 bytes; use the full secret including the whsec_ prefix as the HMAC key.
  4. Compare against v1 using a constant-time comparison.
  5. Reject the request if t is older than 5 minutes (allowing ±60 seconds of clock skew).

Worked example:

InputValue
Secretwhsec_test_secret
Timestamp (t)1739545800
Raw body{"event_id":"abc"}
Signed string1739545800.{"event_id":"abc"}
Expected v16fc0de6006119436f09db0d062c6d181b7e10778533152586b5a03c0b93b73bc

If your implementation produces a different v1 for the same inputs, re-check the body bytes (no re-serialisation), the secret encoding (UTF-8, prefix included), and the hex case (lowercase).

Use the raw body, not a re-serialised object

Most web frameworks parse JSON before your handler runs. Re-serialising the parsed object will not reproduce the bytes that were signed. Capture the raw body bytes before parsing.

Delivery semantics

AspectBehaviour
Success criteriaAny 2xx response. 3xx, 4xx, and 5xx are all treated as failures.
Timeout10 seconds per attempt.
RetriesUp to 4 attempts (initial + 3) with short exponential backoff. 5xx and network errors retry; 4xx fails fast.
OrderingBest-effort — a later event can land before an earlier one's final retry.
DedupeUse event_id from the body. It is stable across retries.
Failure visibilityAfter the final attempt the event is dropped. There is no dead-letter queue, replay endpoint, or failure alert.

Respond 2xx as soon as the event is accepted and queue it for processing — don't do slow work inside the 10-second window. The receiver must be reachable over public TLS; we don't follow 3xx redirects, so an http → https rewrite at your edge will fail silently.

Testing webhooks in sandbox

Webhook events are emitted by real FRP execution against the sandbox, so producing each event means driving the underlying behaviour. Authorise the underlying enduring consent using PNZ auto-approve (see the Testing Guide for credentials), then:

EventHow to produce it in sandbox
completedCreate an FRP with a small amount and start_date of today (before 09:00 NZ), then wait.
cancelledCall DELETE /payments/v1/fixed-recurring-payments/{id} — the cleanest deterministic trigger.
failedNot currently triggerable on demand in sandbox.