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:
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.
Two concepts to keep straight:
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.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.
One-Time Setup:
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.
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:
Authorised statusmaximum_amount_payment or maximum_amount_periodstart_date must be today or in the future (NZ timezone); omit it to default to the later of today and the consent's activation datedaily consents, start_date cannot be today if the request is submitted after 21:45 NZ — set it to tomorrow insteadThe 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.
| Method | Path | Description |
|---|---|---|
POST | /payments/v1/fixed-recurring-payments | Create 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 |
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"
}
| Field | Required | Description |
|---|---|---|
consent_id | yes | An authorised enduring consent. |
amount | yes | Per-payment amount. Must not exceed the consent's maximum_amount_payment or maximum_amount_period. |
pcr | yes | Particulars, Code, and Reference shown on the customer's bank statement. Each field max 12 characters. |
start_date | no | First 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_strategy | no | none (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).
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.
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"
}
| Field | Description |
|---|---|
fixed_recurring_payment_id | Unique identifier for the schedule. |
consent_id | The underlying enduring consent. |
status | active or cancelled. |
start_date | The 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_date | Next scheduled payment date. |
amount | Per-payment amount, as supplied at creation. |
pcr | Particulars, Code, and Reference shown on the customer's bank statement. |
retry_strategy | none or same_day — see Retry Strategies. |
creation_timestamp | When the schedule was created (ISO 8601). |
status_updated_timestamp | When the status was last updated (ISO 8601). |
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.
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:
maximum_amount_period, so a manual payment can leave too little for the next instalment in that period.409 BP703, which names the payment to poll.See Recurring Payments for how to make and retry manual payments.
| Event | Timing |
|---|---|
| First payment | Initiated 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 payments | Anchored 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 window | Ticks 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.
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.
The payment frequency is determined by the underlying enduring consent's period:
| Period | Nth date calculation (N = number of periods elapsed) |
|---|---|
daily | start_date + N days |
weekly | start_date + N weeks |
fortnightly | start_date + (N × 2) weeks |
monthly | start_date + N months |
annual | start_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.
| Strategy | Behaviour |
|---|---|
none | No retries — the schedule advances to the next period on failure. |
same_day | Retries hourly within the same calendar day, then advances to the next period. |
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.
same_dayOn 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.
| Status | Description |
|---|---|
active | Schedule is running and will execute payments |
cancelled | Schedule has been cancelled (manually or automatically) |
Schedules are automatically cancelled when the underlying consent becomes unusable. The timing depends on how the consent ended:
DELETE /enduring-consents/{id} — the FRP is cancelled synchronously as part of the revoke transaction, and the fixed-recurring-payment-cancelled webhook fires immediately.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.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.
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.
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).
| Method | Path | Description |
|---|---|---|
POST | /payments/v1/subscriptions | Create a subscription |
GET | /payments/v1/subscriptions | List 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"
}
| Field | Description |
|---|---|
subscription_id | Unique identifier for the subscription. |
secret | Signing secret (whsec_… prefix). Store immediately — returned only on create. |
callback_url | HTTPS URL BlinkPay POSTs event payloads to. |
event_types | The event types this subscription receives. |
creation_timestamp | When 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.
All three events share a single payload schema. The event type identifies which lifecycle moment fired.
| Event type | Fires when |
|---|---|
urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-completed | An FPE has been accepted by the bank. |
urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-failed | An FPE has failed terminally and the schedule has advanced to the next period. |
urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-cancelled | The 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.
| Field | Type | Notes |
|---|---|---|
event_type | string (URN) | One of the three event types above. |
event_id | UUID | Stable across delivery retries — use this to dedupe. |
timestamp | ISO 8601 with offset | When the event was generated. |
frp_id | UUID | The schedule the event relates to. |
consent_id | UUID | The underlying enduring consent. |
payment_id | UUID, conditional | The 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.
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
To verify:
t and v1 from the header.1739545800.{...body...}. Do not re-serialise the JSON; whitespace and key order matter.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.v1 using a constant-time comparison.t is older than 5 minutes (allowing ±60 seconds of clock skew).Worked example:
| Input | Value |
|---|---|
| Secret | whsec_test_secret |
Timestamp (t) | 1739545800 |
| Raw body | {"event_id":"abc"} |
| Signed string | 1739545800.{"event_id":"abc"} |
Expected v1 | 6fc0de6006119436f09db0d062c6d181b7e10778533152586b5a03c0b93b73bc |
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.
| Aspect | Behaviour |
|---|---|
| Success criteria | Any 2xx response. 3xx, 4xx, and 5xx are all treated as failures. |
| Timeout | 10 seconds per attempt. |
| Retries | Up to 4 attempts (initial + 3) with short exponential backoff. 5xx and network errors retry; 4xx fails fast. |
| Ordering | Best-effort — a later event can land before an earlier one's final retry. |
| Dedupe | Use event_id from the body. It is stable across retries. |
| Failure visibility | After 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.
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:
| Event | How to produce it in sandbox |
|---|---|
completed | Create an FRP with a small amount and start_date of today (before 09:00 NZ), then wait. |
cancelled | Call DELETE /payments/v1/fixed-recurring-payments/{id} — the cleanest deterministic trigger. |
failed | Not currently triggerable on demand in sandbox. |