Create Payment Link API
Create a Herepay payment link programmatically and get back a public pay_url
that you redirect your customer to. The pay page is the same dynamic checkout a
dashboard‑created link uses (FPX bank selection → payment → receipt), so no UI
work is needed on the integrator side — just create the link and redirect.
Endpoint
POST /api/integration/create-payment-link
| Environment | Base URL |
|---|---|
| UAT | https://uat.herepay.org |
| Production | https://app.herepay.org |
Full URL (UAT): https://uat.herepay.org/api/integration/create-payment-link
Authentication
Authenticate with your team's Secret Key, sent as a request header. This is the
same SecretKey used by the other /api/integration/* endpoints.
SecretKey: <your-team-secret-key>
A missing or wrong key returns 401 Unauthorized.
Find your Secret Key in the Herepay dashboard under your team's API/Integration settings. Treat it as a credential — never expose it in client‑side code.
Request
Headers
| Header | Value | Required |
|---|---|---|
SecretKey | Your team secret key | ✅ |
Content-Type | application/json | ✅ |
Accept | application/json | ✅ |
Body parameters
Only title and amount are required. Everything else is optional and has a
sensible default.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
title | string (≤255) | ✅ | — | Name of what's being paid for. Shown on the pay page. |
amount | number | ✅ | — | Amount in MYR. Range 1 – 99,999,999. Two decimals. |
description | string (≤65000) | — | null | HTML allowed. Rendered in the pay page's "Additional Information". |
usage_type | string | — | multiple | multiple = reusable by any number of payers. single = closes after the first successful payment. |
expires_at | datetime | — | null | Link stops accepting payments after this time. Must be in the future. Format YYYY-MM-DD HH:MM:SS. |
team_bank_account_id | integer | — | team default | Settlement account. Must be one of your team's verified bank accounts. |
collect_phone | boolean | — | true | Whether the payer must enter a phone number. |
note_enabled | boolean | — | false | Show an extra note field to the payer. |
note_label | string (≤100) | — | null | Label for the note field. Required when note_enabled is true. |
redirect_url | url (≤2048) | — | null | Browser is redirected here after payment. Falls back to the Herepay receipt page if empty. |
callback_url | url (≤2048) | — | null | Server‑to‑server webhook fired when the payment settles (see Callback). |
notify_email | email (≤255) | — | null | Email to notify about the link (reserved). |
payer_name | string (≤255) | — | null | Prefills the Full Name field on the pay page. |
payer_email | email (≤255) | — | null | Prefills the Email field on the pay page. |
payer_phone | string (≤32) | — | null | Prefills the Phone Number field on the pay page. |
Payer prefill. When
payer_name/payer_email/payer_phoneare set, the pay page opens with the payer form already filled in — the payer can still edit the values before paying. The prefill is stored on the link itself, so it's intended forusage_type: singlelinks created for a specific customer; on amultiplelink every payer would see the same prefilled details.
Examples
Minimal
curl -X POST https://uat.herepay.org/api/integration/create-payment-link \
-H "SecretKey: <your-team-secret-key>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"title": "Consultation Fee",
"amount": 10.50
}'
Full
curl -X POST https://uat.herepay.org/api/integration/create-payment-link \
-H "SecretKey: <your-team-secret-key>" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"title": "Order #A1042",
"amount": 89.90,
"description": "<p>1x Premium Plan — annual</p>",
"usage_type": "single",
"expires_at": "2026-12-31 23:59:59",
"collect_phone": true,
"note_enabled": true,
"note_label": "Booking reference",
"redirect_url": "https://integrator.example.com/return",
"callback_url": "https://integrator.example.com/webhook/herepay",
"notify_email": "sales@integrator.example.com",
"payer_name": "Aisyah binti Ahmad",
"payer_email": "aisyah@example.com",
"payer_phone": "0123456789"
}'
Response
Success — 200 OK
{
"status": 200,
"data": {
"id": 6,
"code": "539ace2630bf7fa2",
"title": "Order #A1042",
"amount": 89.9,
"usage_type": "single",
"status": "active",
"expires_at": null,
"redirect_url": "https://integrator.example.com/return",
"callback_url": "https://integrator.example.com/webhook/herepay",
"payer_name": "Aisyah binti Ahmad",
"payer_email": "aisyah@example.com",
"payer_phone": "0123456789",
"pay_url": "https://uat.herepay.org/herepay/pay/EQFYRVJXFhhCVUwoXwMlYQ"
}
}
| Field | Description |
|---|---|
id | Internal payment‑link id. |
code | Public link code. |
status | active, inactive, expired, or completed. |
expires_at | ISO‑8601 or null. |
pay_url | Redirect your customer here to pay. |
Errors
| HTTP | Body | When |
|---|---|---|
401 | { "status": "Unauthorized", "status_code": "41", "error": "..." } | Missing / invalid SecretKey. |
422 | { "status": 422, "error": { "field": ["message"] } } | Validation failed. |
500 | { "status": 500, "error": "Failed to create payment link." } | Server error. |
Validation example:
{
"status": 422,
"error": {
"title": ["The title field is required."],
"amount": ["The amount field is required."]
}
}
Payment flow
- Create —
POSTthis endpoint → receivepay_url. - Redirect — send your customer to
pay_url(HTTP302, or open in a browser). - Pay — the customer completes the payment on the Herepay pay page via FPX.
- Return — after payment, Herepay:
- redirects the customer's browser to your
redirect_url(or the Herepay receipt page if you didn't set one), and - sends a server‑to‑server callback to your
callback_url(if set).
- redirects the customer's browser to your
Each link uses its own redirect_url / callback_url. If a link doesn't set
them, Herepay falls back to your team‑level URLs.
Callback (webhook)
When a link payment settles, Herepay POSTs (as application/x-www-form-urlencoded)
to the link's callback_url.
Payload
| Field | Description |
|---|---|
reference_code | Invoice reference code. |
payment_code | Payment reference code. |
transaction_id | FPX transaction id (may be empty). |
status | Success / Failed / Pending. |
status_code | 00 success, 30 failed, 29 pending. |
message | Gateway remark. |
amount | Paid amount (MYR). |
currency | MYR. |
payment_method | Payment gateway name. |
fpx_type | FPX type (may be empty). |
bank_name | Payer's bank (may be empty). |
checksum | HMAC signature — see below. |
Verifying the checksum
The checksum lets you confirm the callback really came from Herepay:
- Take all payload fields except
checksum. - Sort them by key (ascending).
- Join the values with a comma
,(in the sorted‑key order). Array values arejson_encoded first. - Compute
HMAC‑SHA256of that string using your team's private key. - Compare (constant‑time) against the received
checksum.
checksum = HMAC_SHA256( implode(",", values_sorted_by_key), team_private_key )
Respond 200 OK to acknowledge. Herepay logs a failure if your endpoint errors or
times out (5s).
Notes
- Amounts are in MYR with two decimals.
usage_type: singlelinks close after the first successful payment; reusable links (multiple) can be paid any number of times.- The
pay_urlcode is encrypted — it reveals nothing about the underlying record and is the same URL space used by custom invoices. - The link's optional tip/contribution section is shown on the pay page.