HEALTHPAY DEVELOPERS

Build payments into your product

Create secure hosted checkouts, choose available payment methods, and receive status updates through a single integration.

Start integrating Postman collection
QUICKSTART

Accept your first payment

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.

1Create an orderPOST amount & currency, get back a UUID
2Get a checkout URLExchange the UUID, redirect the customer
3Webhook confirmsSigned payment.succeeded/failed
ENVIRONMENTS & HOSTS

Two hosts, one key

TESTAuthorization: Bearer sk_test_...

"environment": "test" in the order body. Isolated data, no real money moves.

LIVEAuthorization: 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 orders
  • https://dashboard.healthpay.com.eg/api/v1 — checkout links, refunds, invoices, subscriptions, balance
AUTHENTICATION

One header, everywhere

Every request — on either host — carries the same header:

Header
Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

There 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.

CREATE AN ORDER

Request

POST https://gateway.pg.healthpay.com.eg/v1/orders
{
  "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.

Response

Returns a checkout UUID — not yet a payable link:

201
{ "uuid": "9063b7f9-c900-4e89-b1ef-caa145c3efa3", "channels": ["visa/master"] }
CHECKOUT URL

Turn the order into something payable

A second call, on the other host, exchanges the UUID for a hosted checkout link:

POST https://dashboard.healthpay.com.eg/api/v1/orders/{uuid}/checkout-url
{
  "customer_name": "Ahmed Ali",
  "customer_email": "customer@example.com",
  "customer_mobile": "+201001234567"
}

Response:

200
{ "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.

WEBHOOKS

Treat the webhook as the source of truth

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

Payload — POST to your endpoint
{
  "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):

Node.js
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));
PHP
$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'] ?? '');
  • Respond 2xx quickly — verify the signature before doing slow work
  • Make processing idempotent — the same event can be redelivered
  • Failed deliveries retry automatically; dead-lettered ones can be replayed manually from the Developer page
SAVE A CARD

Save a card on your own platform

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:

POST https://gateway.pg.healthpay.com.eg/v1/card-sessions
{
  "origin": "https://your-store.example.com"
}

Response:

201
{ "token": "eyJtZXJjaGFudElkIjoi...signature", "expiresAt": 1758633600 }

Then embed the iframe on that same origin, using your own identifier for this customer:

HTML
<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.

MERCHANT API

Manage your account directly

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.

sk_test_…Test keyOnly reaches test-mode data
sk_live_…Live keyReal orders and money
Authorization header
Authorization: Bearer sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Every 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.

ORDERS

Create, look up and cancel orders

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).

POST/v1/ordersCreate an order
GET/v1/orders/{id}Look up by order id
GET/v1/orders/uuid/{uuid}Look up by checkout UUID
POST/v1/orders/{id}/cancelCancel (refused once paid or mid-flight)
POST/api/v1/orders/{uuid}/checkout-urlTurn a created order into a payable link — this dashboard, not the gateway host
REFUNDS

Request a refund for a settled order

Every request is queued for admin review, then executed at the payment provider on approval — the same flow as the dashboard's Refunds page.

GET/api/v1/refundsList your refund requests
GET/api/v1/refunds/{id}Look up one
POST/api/v1/refundsRequest a refund
POST /api/v1/refunds
{
  "order_uuid": "b6e2b6b0-4b0e-4c8e-9c2a-6f6b8f2b7a1a",
  "amount": 250.00,
  "reason": "Customer requested a partial refund"
}
INVOICES

Itemised, due-dated bills

A draft invoice becomes payable only once sent — it turns into a normal checkout link sized to the invoice total.

GET/api/v1/invoicesList
POST/api/v1/invoicesCreate a draft
GET/api/v1/invoices/{uuid}Look up one
PUT/api/v1/invoices/{uuid}Edit a draft
DELETE/api/v1/invoices/{uuid}Delete a draft
POST/api/v1/invoices/{uuid}/sendSend — makes it payable
POST/api/v1/invoices/{uuid}/voidVoid (blocked once paid)
POST /api/v1/invoices
{
  "customer_name": "Ahmed Ali",
  "customer_email": "customer@example.com",
  "currency": "EGP",
  "due_date": "2026-10-01",
  "items": [
    { "description": "Consultation", "quantity": 1, "unit_price": 500 }
  ]
}
SUBSCRIPTIONS

Recurring invoicing

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.

GET/api/v1/subscriptionsList
POST/api/v1/subscriptionsCreate
GET/api/v1/subscriptions/{uuid}Look up one
PUT/api/v1/subscriptions/{uuid}Edit (applies next cycle)
DELETE/api/v1/subscriptions/{uuid}Delete (only before first bill)
POST/api/v1/subscriptions/{uuid}/pausePause
POST/api/v1/subscriptions/{uuid}/resumeResume
POST/api/v1/subscriptions/{uuid}/cancelCancel
POST /api/v1/subscriptions
{
  "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 }
  ]
}
BALANCE & SETTLEMENTS

Read-only account figures

Routing and fee configuration remain admin-only platform controls and are not exposed here.

GET/api/v1/balanceCurrent ledger balance
GET/api/v1/settlementsSettlement batch history
REFERENCE

Interactive API reference

Every 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).

PLUGINS

WooCommerce

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).

TESTING

Sandbox test cards

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 · Success
4111 1111 1111 1112Visa · Hard decline (insufficient funds)
4111 1111 1111 1119Visa · Declined — token revoked
4111 1111 1111 1120Visa · Simulated PSP timeout (504)
5123 4567 8901 2346Mastercard · Success
5123 4567 8901 2347Mastercard · 3DS2 challenge required
5078 0362 3198 5581Meeza · Success
5078 0362 3198 5582Meeza · 3DS2 frictionless

The 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.

CURRENCIES

Supported currencies

EGP EGP
ERROR HANDLING

Build predictable recovery

Validation failures return HTTP 422 with a structured breakdown, not a generic message:

422
{
  "statusCode": 422,
  "errorCode": "VALIDATION_ERROR",
  "message": "Validation failed",
  "details": [ { "field": "amount", "violation": "Amount must be greater than zero" } ]
}
StatusMeaning
401Missing or malformed Authorization header
403Valid key, but a read-only key was used for a write, or the resource belongs to a different merchant
404No such order/refund/invoice/subscription for this merchant
422Request body failed validation — see details
429Too many requests for this key — see Retry-After
504Simulated or real PSP timeout

Display a safe customer message, retain the order reference, and log the response without storing card data or credentials.