Authorization: Bearer sk_test_..."environment": "test" in the order body. Isolated data, no real money moves.
Create secure hosted checkouts, choose available payment methods, and receive status updates through a single integration.
Generate a test key from the merchant dashboard, create an order, exchange it for a checkout URL, and redirect the customer there. Test and live data are fully isolated — nothing you do with a sk_test_ key touches real money.
payment.succeeded/failedAuthorization: Bearer sk_test_..."environment": "test" in the order body. Isolated data, no real money moves.
Authorization: Bearer sk_live_..."environment": "live" — requires an approved merchant account with live payments enabled.
The API itself is split across two hosts — the same key works on both:
https://gateway.pg.healthpay.com.eg — create/look up/cancel ordershttps://dashboard.healthpay.com.eg/api/v1 — checkout links, refunds, invoices, subscriptions, balanceEvery request — on either host — carries the same header:
Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxThere is no key in the request body and no separate public/secret pair. Generate a key from Dashboard → Developer → API keys (see API keys below) — it's shown once, copy it immediately.
{
"environment": "test",
"reference": "ORDER-10482",
"amount": 1000,
"currency": "EGP",
"apiOperation": "PAY",
"notificationUrl": "https://merchant.example.com/webhooks/healthpay",
"city": "CAIRO",
"country": "EGY",
"street": "12 Tahrir Street"
}Use a unique reference. It's your own order id, kept alongside ours so you can reconcile the two. notificationUrl is required by the schema but is not where delivery actually happens — see Webhooks for the real delivery mechanism.
Returns a checkout UUID — not yet a payable link:
{ "uuid": "9063b7f9-c900-4e89-b1ef-caa145c3efa3", "channels": ["visa/master"] }A second call, on the other host, exchanges the UUID for a hosted checkout link:
{
"customer_name": "Ahmed Ali",
"customer_email": "customer@example.com",
"customer_mobile": "+201001234567"
}Response:
{ "checkoutUrl": "https://pay.healthpay.com.eg/...", "expiresAt": "2026-09-22T15:30:00.000Z" }Redirect the customer to checkoutUrl. Calling this again for the same order returns the same link rather than minting a second one.
The customer's browser redirect back to you is for their experience only — never mark an order paid from it. Configure a real delivery endpoint on Dashboard → Developer → Webhooks & API: an HTTPS URL and (once saved) a signing secret. Events fire automatically once that's set — nothing to opt into per order.
payment.succeeded · payment.failed · invoice.paid · invoice.voided · subscription.cancelled · refund.completed · refund.failed
{
"event": "payment.succeeded",
"orderId": "9063b7f9-c900-4e89-b1ef-caa145c3efa3",
"status": "succeeded",
"amount": 1000,
"currency": "EGP",
"channel": "visa/master",
"occurredAt": "2026-09-22T15:31:04.000Z"
}Every delivery carries X-HP-Signature: sha256=<hex> — an HMAC-SHA256 of the exact raw request body, using your signing secret. Verify it against the raw bytes you received, not a re-encoded copy (key order/whitespace can differ and break the match):
const crypto = require('crypto');
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedHeader));$rawBody = file_get_contents('php://input'); // not json_encode($_POST) — must be the raw bytes
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
$valid = hash_equals($expected, $_SERVER['HTTP_X_HP_SIGNATURE'] ?? '');To let a customer save a card on your own site or app — not through a HealthPay-hosted checkout — embed card-iframe. The card is tokenized straight from your customer's browser into HealthPay's vault; the raw number never touches your servers, and you get back only a token. Request a short-lived session server-side first:
{
"origin": "https://your-store.example.com"
}Response:
{ "token": "eyJtZXJjaGFudElkIjoi...signature", "expiresAt": 1758633600 }Then embed the iframe on that same origin, using your own identifier for this customer:
<iframe
src="https://card-iframe.healthpay.com.eg/?merchant_id=YOUR_MERCHANT_ID&customer_id=YOUR_CUSTOMER_ID#session=eyJtZXJjaGFudElkIjoi...signature"
title="Save card">
</iframe>
window.addEventListener('message', (event) => {
if (event.origin !== 'https://card-iframe.healthpay.com.eg') return;
if (event.data?.type === 'healthpay.card.tokenized') {
// event.data.token — store this, not the card. Use it later the same
// way a saved-card charge works anywhere else on the platform.
}
});The session is bound to the exact origin you requested it for and expires in 5 minutes by default (up to 15 — pass ttlSeconds). Request a new one per page load; don't cache it.
Beyond the hosted checkout above, an API key lets you manage orders, refunds, invoices, subscriptions and settlement history directly from your own backend. Generate one from Dashboard → Developer → API keys — pick Test or Live, and copy the secret immediately, it's shown only once.
Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxEvery endpoint below is scoped to the merchant that owns the key — there is no way to read or modify another merchant's data, even with a valid key. Each key is also either Full access or Read-only (a read-only key gets 403 on any non-GET request) — pick read-only for anything that only needs to pull data, like an accounting integration.
Order creation/lookup lives on the gateway itself — https://gateway.pg.healthpay.com.eg. Turning one into a payable link is the one exception, served here instead (see Get a checkout URL).
/v1/ordersCreate an order/v1/orders/{id}Look up by order id/v1/orders/uuid/{uuid}Look up by checkout UUID/v1/orders/{id}/cancelCancel (refused once paid or mid-flight)/api/v1/orders/{uuid}/checkout-urlTurn a created order into a payable link — this dashboard, not the gateway hostEvery request is queued for admin review, then executed at the payment provider on approval — the same flow as the dashboard's Refunds page.
/api/v1/refundsList your refund requests/api/v1/refunds/{id}Look up one/api/v1/refundsRequest a refund{
"order_uuid": "b6e2b6b0-4b0e-4c8e-9c2a-6f6b8f2b7a1a",
"amount": 250.00,
"reason": "Customer requested a partial refund"
}A draft invoice becomes payable only once sent — it turns into a normal checkout link sized to the invoice total.
/api/v1/invoicesList/api/v1/invoicesCreate a draft/api/v1/invoices/{uuid}Look up one/api/v1/invoices/{uuid}Edit a draft/api/v1/invoices/{uuid}Delete a draft/api/v1/invoices/{uuid}/sendSend — makes it payable/api/v1/invoices/{uuid}/voidVoid (blocked once paid){
"customer_name": "Ahmed Ali",
"customer_email": "customer@example.com",
"currency": "EGP",
"due_date": "2026-10-01",
"items": [
{ "description": "Consultation", "quantity": 1, "unit_price": 500 }
]
}A subscription is a template — it auto-generates and sends a normal invoice every billing cycle. No card is ever charged silently; the customer pays each cycle's invoice the same way as a one-off one.
/api/v1/subscriptionsList/api/v1/subscriptionsCreate/api/v1/subscriptions/{uuid}Look up one/api/v1/subscriptions/{uuid}Edit (applies next cycle)/api/v1/subscriptions/{uuid}Delete (only before first bill)/api/v1/subscriptions/{uuid}/pausePause/api/v1/subscriptions/{uuid}/resumeResume/api/v1/subscriptions/{uuid}/cancelCancel{
"customer_name": "Ahmed Ali",
"customer_email": "customer@example.com",
"currency": "EGP",
"interval_unit": "month",
"interval_count": 1,
"due_days": 7,
"items": [
{ "description": "Monthly plan", "quantity": 1, "unit_price": 300 }
]
}Routing and fee configuration remain admin-only platform controls and are not exposed here.
/api/v1/balanceCurrent ledger balance/api/v1/settlementsSettlement batch historyEvery refunds/invoices/subscriptions/balance endpoint above, with full request/response schemas — browse it interactively or pull the raw spec into your own tooling (Postman, an SDK generator, a contract test).
Drop-in payment gateway for WooCommerce — cards, Fawry, Meeza, and mobile wallets via your API key, with a signature-verified webhook as the source of truth for payment status (an order is never marked paid from the customer's browser redirect alone).
Use a test-mode order with any of these card numbers (through the hosted checkout page) for a deterministic outcome — none of them touch a real PSP, so the result never depends on a sandbox being up.
4111 1111 1111 1111Visa · Success4111 1111 1111 1112Visa · Hard decline (insufficient funds)4111 1111 1111 1119Visa · Declined — token revoked4111 1111 1111 1120Visa · Simulated PSP timeout (504)5123 4567 8901 2346Mastercard · Success5123 4567 8901 2347Mastercard · 3DS2 challenge required5078 0362 3198 5581Meeza · Success5078 0362 3198 5582Meeza · 3DS2 frictionlessThe 3DS2 rows return a recognizable status marker for your own branching logic, not a working challenge screen — 3DS2 itself is not yet live on this platform. The full table (with expiry/CVV) is also on Dashboard → Developer → Webhooks & API.
Validation failures return HTTP 422 with a structured breakdown, not a generic message:
{
"statusCode": 422,
"errorCode": "VALIDATION_ERROR",
"message": "Validation failed",
"details": [ { "field": "amount", "violation": "Amount must be greater than zero" } ]
}| Status | Meaning |
|---|---|
401 | Missing or malformed Authorization header |
403 | Valid key, but a read-only key was used for a write, or the resource belongs to a different merchant |
404 | No such order/refund/invoice/subscription for this merchant |
422 | Request body failed validation — see details |
429 | Too many requests for this key — see Retry-After |
504 | Simulated or real PSP timeout |
Display a safe customer message, retain the order reference, and log the response without storing card data or credentials.