Webhooks
StudentPay → provider outbound webhooks only.
This page does not document inbound GoCardless webhooks.
Configuration
Distinguish code capability from environment configuration.
| Layer | Current state |
|---|---|
| Code | The NZ API can emit the events listed below, with HMAC signatures and in-request retries |
| Environment configuration | Delivery runs only when StudentPay has set a destination URL and signing secret for that environment |
| Production | Provider webhook destinations are not generally configured. Do not assume live Production delivery |
Until a destination is configured, GET /v1/environment may show
readiness.webhook_destinations_configured: false. Create, GET, and confirm
still succeed. Poll GET for setup complete and agreement ids.
Ask StudentPay to configure sandbox first. Do not treat this page as proof that your Production account already receives webhooks.
Implemented
Only these events are emitted by the NZ API.
| Event | When | Replay |
|---|---|---|
checkout.created |
New provider checkout created | Not sent on idempotent create replay |
checkout.confirmed |
Enrolment confirmed | Not sent when already_confirmed |
agreement.generated |
Agreement PDF stored during that confirm | Only if pdf_generated is true; not sent on confirm replay |
No direct_debit.*, payment, or arrears events are implemented.
Envelope
{
"event_id": "evt_…",
"event": "checkout.created",
"created_at": "2026-08-13T01:40:00.000Z",
"request_id": "req_…",
"environment": "sandbox",
"data": {}
}
Treat event_id as the idempotency key on your side. Consume each event_id
once. Correlate with data.provider_order_id and data.checkout_id.
The API retries in-request up to 3 times (0 / 250 / 750 ms) with the same
event_id and signed body. Your endpoint should acknowledge with HTTP 2xx
when the event is accepted, including replays of the same event_id.
Signature
| Header | Purpose |
|---|---|
X-StudentPay-Event |
Event name |
X-StudentPay-Event-Id |
evt_… |
X-StudentPay-Timestamp |
Unix seconds used in the signature |
X-StudentPay-Signature |
t=<timestamp>,v1=<hmac_sha256_hex> |
X-StudentPay-Delivery-Attempt |
1-based attempt |
X-StudentPay-Delivery-Max-Attempts |
Default 3 |
X-Request-Id |
Correlated API request id when available |
Canonical string: timestamp, then ., then the raw JSON body. HMAC-SHA256 with the configured webhook secret.
checkout.created data
{
"provider_code": "SANDBOX_DEMO",
"provider_order_id": "DEMO-ENROLMENT-1001",
"checkout_id": "SANDBOX_DEMO-1720000000000-AB12CD34",
"opportunity_id": "006XXXXXXXXXXXX",
"dda_id": "a0XXXXXXXXXXXXX",
"contact_id": "003XXXXXXXXXXXX"
}
checkout.confirmed data
{
"provider_code": "SANDBOX_DEMO",
"provider_order_id": "DEMO-ENROLMENT-1001",
"checkout_id": "SANDBOX_DEMO-1720000000000-AB12CD34",
"opportunity_id": "006XXXXXXXXXXXX",
"dda_id": "a0XXXXXXXXXXXXX",
"status": "Confirmed",
"payment_plan_agreement_id": "a0JXXXXXXXXXXXX"
}
agreement.generated data
{
"provider_code": "SANDBOX_DEMO",
"provider_order_id": "DEMO-ENROLMENT-1001",
"checkout_id": "SANDBOX_DEMO-1720000000000-AB12CD34",
"opportunity_id": "006XXXXXXXXXXXX",
"payment_plan_agreement_id": "a0JXXXXXXXXXXXX",
"payment_plan_agreement_number": "PPA-000001",
"pdf_content_document_id": "069XXXXXXXXXXXX",
"agreement_version": "2026-08-02"
}
Not implemented
Do not build handlers for these. They are not emitted.
direct_debit.authoriseddirect_debit.faileddirect_debit.setup_completedpayment_plan.activatedpayment.failedpayment.confirmedplan.in_arrearsplan.cancelled