JavaScript is required to view this page
Webhooks

Webhooks

A webhook is a signed notification that a consent, payment or refund changed status, or that a Fixed Recurring Payment instalment completed, failed or was cancelled. It carries IDs only: call the GET endpoint for the details. You subscribe at merchant-account level, and BlinkPay POSTs each subscribed event to your callback URL.

Webhooks are best effort

An event can occasionally arrive more than once, or not at all if either side fails. Deduplicate on event_id and reconcile through the GET endpoints.

What Webhooks Don't Do

The absence of an event is not a status: if nothing has arrived, read the resource rather than assuming it hasn't changed. Webhooks remove routine polling, not the periodic reconcile, so keep a sweep that reads anything still open. There is no ordering promise: a later event can arrive before an earlier one, so never infer status from arrival order. Nothing is sent after a consent or payment reaches a final status.

Known Limitations

  • A bank-side revocation of a dormant enduring consent (one you aren't making payments on) is only discovered on the next payment attempt or GET, and the consent-updated event fires then.
  • A subscription created after a status change doesn't receive that change.
  • A status BlinkPay corrects manually sends no webhook.

Events

Every event type starts with urn:nz:co:blinkpay:debit:events:.

Event typeFires whenIDs carried
urn:nz:co:blinkpay:debit:events:consent-updatedA consent becomes Authorised, Rejected, Revoked or GatewayTimeout, for every consent type and flow, including decoupled and card; or an enduring consent becomes Consumedconsent_id
urn:nz:co:blinkpay:debit:events:payment-updatedA payment becomes AcceptedSettlementCompleted or Rejected, including Fixed Recurring Payment instalmentsconsent_id, payment_id
urn:nz:co:blinkpay:debit:events:refund-updatedA card refund becomes completed or failedconsent_id, payment_id, refund_id
urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-completedAn instalment has been accepted by the bankconsent_id, frp_id, payment_id
urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-failedAn instalment has failed terminally and the schedule has advanced to the next periodconsent_id, frp_id, payment_id if a payment record was created
urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-cancelledThe schedule has been cancelled, through the API or because the consent was revoked or expiredconsent_id, frp_id, payment_id only if the cancellation interrupted an instalment whose payment had already been submitted

A single consent or quick payment that becomes Consumed sends no consent-updated; the payment-updated for its payment covers it. An account_number refund sends no event: no money moves and it is completed as soon as you create it. A card refund still processing can take up to about 12 hours to send its event, unless you read the refund first.

An instalment that created a payment raises both its Fixed Recurring Payment event and payment-updated, so subscribing to both delivers two events for it. Use frp_id and consent_id as your correlation keys for Fixed Recurring Payment events.

A fixed-recurring-payment-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. 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 their payment details, but don't end their subscription. cancelled means "the schedule itself is gone and no more debits will happen": typically revert the customer to a non-active state and ask them to re-authorise if you want them back.

Payload

FieldTypeNotes
event_typestring (URN)One of the six event types above.
event_idUUIDUnique to the status change, and the same across retries and across your subscriptions. Deduplicate on it.
timestampISO 8601 with offsetWhen BlinkPay recorded the status change, in NZ time.
consent_idUUIDThe consent the event belongs to. Always present.
payment_idUUID, conditionalPresent on payment and refund events, and on Fixed Recurring Payment events as listed above.
refund_idUUID, conditionalPresent on refund events.
frp_idUUID, conditionalPresent on the three Fixed Recurring Payment events only; an instalment's payment-updated does not carry it.

A field that doesn't apply to an event is absent, not null, so check for the key's presence. Payloads carry no names, account numbers, amounts or PCR fields; read those with your normal API credentials.

Consent:

{
  "event_type": "urn:nz:co:blinkpay:debit:events:consent-updated",
  "event_id": "23456789-2345-2345-2345-234567890123",
  "timestamp": "2026-10-01T09:45:00+13:00",
  "consent_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
}

Payment:

{
  "event_type": "urn:nz:co:blinkpay:debit:events:payment-updated",
  "event_id": "12345678-1234-1234-1234-123456789012",
  "timestamp": "2026-10-01T09:15:04+13:00",
  "consent_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "payment_id": "87654321-4321-4321-4321-210987654321"
}

Refund:

{
  "event_type": "urn:nz:co:blinkpay:debit:events:refund-updated",
  "event_id": "34567890-3456-3456-3456-345678901234",
  "timestamp": "2026-10-02T18:04:12+13:00",
  "consent_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "payment_id": "87654321-4321-4321-4321-210987654321",
  "refund_id": "45678901-4567-4567-4567-456789012345"
}

Fixed Recurring Payment:

{
  "event_type": "urn:nz:co:blinkpay:debit:events:fixed-recurring-payment-completed",
  "event_id": "56789012-5678-5678-5678-567890123456",
  "timestamp": "2026-11-01T09:00:41+13:00",
  "consent_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "frp_id": "11111111-2222-2222-2222-333333333333",
  "payment_id": "98765432-9876-9876-9876-987654321098"
}

Subscribing

Subscriptions are managed through the API and apply to your whole merchant account. Each one lists the event types it receives, and you can register several, for example to send different events to different URLs. The access token needs the create:subscription, view:subscription or delete:subscription scope.

MethodPathDescription
POST/payments/v1/subscriptionsCreate a subscription
GET/payments/v1/subscriptionsList subscriptions
DELETE/payments/v1/subscriptions/{subscription_id}Delete 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:consent-updated",
    "urn:nz:co:blinkpay:debit:events:payment-updated",
    "urn:nz:co:blinkpay:debit:events:refund-updated"
  ]
}

Response:

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

The callback URL must be HTTPS in production and on the same whitelist as your redirect URIs (Client Portal, Settings > API Settings, Whitelisted Redirect URLs, or ask support). A URL that isn't on it is refused with 400 BP214. Sandbox and production subscriptions are separate, with separate secrets and separate whitelists, so create the subscription again when you go live.

There is no pause: to stop deliveries, delete the subscription. To rotate a secret, create a new subscription, verify its events, then delete the old one; while both exist, each receives every matching event signed with its own secret.

Verifying the Signature

Each delivery carries two headers besides Content-Type: application/json:

X-Signature: t=1739545800,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
X-Idempotency-Key: 12345678-1234-1234-1234-123456789012

t is a Unix timestamp, re-stamped on every attempt, so a retry carries a new t and a new v1. To verify:

  1. Parse t and v1 from the header.
  2. Build the signed string from the timestamp, a literal dot and the raw request body bytes, for example 1739545800.{...body...}.
  3. Compute HMAC-SHA256(signed_string, your_signing_secret) and hex-encode it in lowercase. Treat the signed string and the secret as UTF-8 bytes, and use the full secret, including the whsec_ prefix, as the key.
  4. Compare against v1 with a constant-time comparison.
  5. Reject the request if t is more than 5 minutes old (allowing ±60 seconds of clock skew).
InputValue
Secretwhsec_test_secret
Timestamp (t)1739545800
Raw body{"event_id":"abc"}
Signed string1739545800.{"event_id":"abc"}
Expected v16fc0de6006119436f09db0d062c6d181b7e10778533152586b5a03c0b93b73bc

If you get a different v1 for the same inputs, re-check the body bytes, the secret encoding (UTF-8, prefix included) and the hex case.

Use the raw body, not a re-serialised object

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

Retries

Any 2xx response is a successful delivery. Connection errors, timeouts, 5xx and 429 are retried; other 4xx and 3xx fail fast. Your endpoint has 10 seconds to respond to each attempt, and there are up to 4 attempts (initial + 3). An event that fails every attempt is not sent again; your periodic reconcile picks up the change.

Reading the resource and updating your records normally fits inside the 10 seconds. If your handling can take longer, respond 2xx first and finish afterwards: an attempt that times out is retried, so the event arrives again. Because redirects are not followed, a callback URL that redirects, for example to add www or a trailing slash, fails every delivery.

Deduplicating

An event only tells you to read the resource, so a repeat is harmless if you update your records only where they don't already match its status. Where an event triggers something that must happen once, such as an email to the customer, key it on event_id; X-Idempotency-Key carries the same value.

Testing in Sandbox

Each event comes from real sandbox activity, so walk a consent or payment through the flow with the sandbox test values. Create a sandbox subscription first; its callback URL must be on your sandbox whitelist, where http:// and localhost are allowed.

EventHow to produce it in sandbox
consent-updatedAuthorise a consent: the decoupled auto-approve number is quickest. Cancel on the PNZ login page for Rejected; call the consent's DELETE endpoint after authorising it for Revoked; leave a gateway consent without choosing a bank for GatewayTimeout.
payment-updatedMake a payment on an authorised consent, or complete a quick payment, and wait for AcceptedSettlementCompleted. Cancelling a quick payment on the PNZ login page gives a Rejected payment.
refund-updatedRefund a sandbox card payment with full_refund or partial_refund, if card refunds are enabled on your sandbox account.
fixed-recurring-payment-completedCreate a Fixed Recurring Payment with a small amount and a start_date of today, before 09:00 NZ time, then wait.
fixed-recurring-payment-cancelledCall DELETE /payments/v1/fixed-recurring-payments/{fixed_recurring_payment_id}.
fixed-recurring-payment-failedNot triggerable on demand in the sandbox.

What's Changed

A Gateway consent whose customer leaves without choosing a bank now becomes GatewayTimeout once the 15-minute Gateway session cutoff passes, even if you don't poll, and consent-updated fires for it. Its status_updated_timestamp can therefore be earlier than your polls used to see. A customer who cancels at the Gateway after that leaves it GatewayTimeout rather than Rejected. See Timeouts and Deadlines.