Skip to content

HyperPay Payment Gateway Integration Cheatsheet

  • HyperPay is a GCC-focused payment service provider (PSP) that exposes REST payment APIs on top of the OPP/oppwa gateway, using base URLs like https://eu-test.oppwa.com for sandbox and https://oppwa.com for production.
  • Integrations are built around creating a checkout session (/v1/checkouts) and then executing or verifying the payment using that checkout identifier or a resourcePath returned from the widget/SDK.

Commonly enabled brands (configured per merchant/entity):

  • Card schemes: VISA, MASTER, AMEX.
  • Local Saudi methods: MADA.
  • Wallets: STC_PAY, APPLEPAY (via specific entities and brand values).
  • Brands are specified either in the hosted widget (data-brands="VISA MASTER MADA STC_PAY APPLEPAY") or at the SDK level, and mapped to specific entity IDs on your backend.
  • Hosted Checkout / Payment Widgets

    • Backend calls POST /v1/checkouts to create a checkout session.
    • Frontend loads paymentWidgets.js from https://test.oppwa.com/v1/paymentWidgets.js?checkoutId={id} and renders a hosted card/wallet form with configured brands.
    • Customer completes payment on HyperPay-hosted UI; browser is redirected to your shopperResultUrl with id and resourcePath parameters for finalization.
  • Server-to-Server (S2S) / Direct API

    • Your backend collects card details (or uses stored registrations) and sends them directly to HyperPay via REST endpoints such as /v1/payments or /v1/registrations/{id}/payments.
    • Requires full PCI-DSS scope (SAQ D) and is typically avoided unless you are already a card-data environment.
  • Mobile SDK / Native Apps

    • Your backend still creates the checkout via POST /v1/checkouts.
    • The mobile SDK (Android/iOS/Flutter) takes checkoutId and brands and opens a native payment UI; upon completion it returns a resourcePath or success flag and you verify server-side.
  • entityId: Identifier of a merchant entity and payment method configuration (you usually have separate entity IDs for MADA, VISA/MASTER, APPLEPAY, etc.).
  • checkoutId (id): ID returned from POST /v1/checkouts, used by widgets/SDKs to render the payment form and later to query status (/v1/checkouts/{id}/payment).
  • paymentId: Identifier of an executed payment used for capture/refund/reversal APIs (/v1/payments/{paymentId}).
  • resourcePath: OPP/HyperPay-relative path returned to your frontend (e.g. /v1/checkouts/{id}/payment), which your backend later calls with GET {resourcePath}?entityId=... to verify payment.
  • result.code: Gateway-level result code (e.g. 000.200.100 for “successfully created checkout”) indicating the outcome of an operation.

ModeHow it worksPCI impactTypical use cases
Hosted Payment Page / Payment WidgetsBackend creates checkout; frontend loads paymentWidgets.js and renders iframe/embedded form for brandsMinimal (SAQ A) as card data never touches your serversWeb checkouts, marketplaces, SaaS portals
Server-to-Server (Direct API)Backend collects card details and calls /v1/payments (or registrations) directlyHigh (SAQ D) – you handle PAN/CVVLegacy systems, when you already are PCI-DSS certified to store/process card data
Mobile SDKBackend creates checkout, mobile SDK renders native UI, then calls your server with resourcePath to finalizeMobile app is partly in PCI scope but server can remain SAQ A–like modelNative mobile apps needing better UX and wallets (Apple Pay, Google Pay, mada, STC Pay)

Hosted Payment Page / Widgets

  • ✅ Card and wallet data never pass through your backend; lowest PCI burden.
  • ✅ Quickest integration path; HyperPay handles 3DS flows and scheme UX.
  • ✅ Easy to support multiple brands (MADA, STC Pay, Apple Pay) via data-brands.
  • ⚠️ Less control over UI/UX; customizations limited to themes/options.
  • Use for most web applications, especially when you do not want PCI scope.

Server-to-Server (Direct API)

  • ✅ Full control over UX and routing (e.g., custom 3DS flows, card-on-file logic).
  • ✅ Easier to orchestrate multi-gateway routing in a central payment service.
  • ⚠️ Requires secure collection and transmission of card data; PCI-DSS SAQ D applies and you must avoid storing CVV beyond authorization.
  • ⚠️ Increased liability and audit surface.
  • Use only if you are already PCI-certified and have a strong reason to own card entry.

Mobile SDK

  • ✅ Best native UX; SDK handles wallet integrations and 3DS per-platform.
  • ✅ SDK never exposes raw card data to the app in most flows; it talks directly to HyperPay.
  • ⚠️ Requires integrating platform-specific SDKs and handling custom URL schemes (shopperResultUrl).
  • Use when you have native apps and want first-class Apple Pay / mada / STC Pay UX.

  • Sandbox base URL: https://eu-test.oppwa.com with POST /v1/checkouts and related endpoints.
  • Live base URL: https://oppwa.com (or a region-specific equivalent) with identical path structure.
  • Payment widget script URLs map 1:1: https://test.oppwa.com/v1/paymentWidgets.js vs https://oppwa.com/v1/paymentWidgets.js.

Two patterns are visible in production code:

  • Bearer token (recommended)

    • Authorization: Bearer {REST_API_TOKEN} in HTTP headers.
    • Body contains entityId, amount, currency, paymentType, and optional customer/billing fields.
  • UserId/password (legacy)

    • Some older examples pass authentication.userId, authentication.password, and authentication.entityId as body/query parameters to /v1/checkouts and status endpoints.
    • Prefer migrating to Bearer token where available; confirm with HyperPay support.
  • HYPERPAY_BASE_URLhttps://eu-test.oppwa.com (test) vs https://oppwa.com (live).
  • HYPERPAY_TOKEN – REST API token used as Bearer token.
  • Entity IDs by brand / use-case:
    • HYPERPAY_CREDIT_ID (VISA/MASTER), HYPERPAY_MADA_ID, HYPERPAY_APPLE_ID.
  • Currency (e.g., SAR) and mode flags (test vs live) held in config.
  • Use Authorization: Bearer {token} for all backend-to-HyperPay calls where supported.
  • Use Content-Type: application/x-www-form-urlencoded or JSON depending on the specific endpoint and documentation; most public examples use URL-encoded form data for /v1/checkouts.
  • Ensure TLS 1.2+ support in your JVM and outbound proxies; many gateways reject TLS 1.0/1.1.
  • Keep separate config for test and live:
    • Tokens, entity IDs, base URLs, webhook URLs.

  1. Create checkout on merchant backend: POST {baseUrl}/v1/checkouts with entityId, amount, currency, paymentType, etc.
  2. Return checkoutId to frontend/mobile client.
  3. Present payment UI:
    • Web: load paymentWidgets.js and render <form class="paymentWidgets" data-brands="...">.
    • Mobile: call SDK’s checkoutReadyUI / pay with checkoutId and brands.
  4. Customer completes payment and is redirected to your shopperResultUrl or in-app callback.
  5. Backend verifies status by calling GET {baseUrl}{resourcePath}?entityId=... or GET /v1/checkouts/{checkoutId}/payment?entityId=... using same entityId and credentials.
  6. Backend updates order status and returns success/failure to UI.

Sequence diagram – Hosted Payment Widgets

Section titled “Sequence diagram – Hosted Payment Widgets”
User Browser -> Merchant FE: Click "Pay"
Merchant FE -> Merchant BE: POST /orders/{orderId}/pay
Merchant BE -> HyperPay: POST {baseUrl}/v1/checkouts (Bearer token, entityId, amount, currency, paymentType=DB)
HyperPay -> Merchant BE: 200 { id: checkoutId, result.code: "000.200.100" }
Merchant BE -> Merchant FE: checkoutId
Merchant FE -> Browser: Render checkout page with paymentWidgets.js?checkoutId={checkoutId}
User Browser -> HyperPay Widget: Enter card / wallet details, submit
HyperPay Widget -> HyperPay: Process payment (3DS, routing, etc.)
HyperPay -> User Browser: Redirect to shopperResultUrl?id={checkoutId}&resourcePath={path}
User Browser -> Merchant FE: GET /payment/result?{query}
Merchant FE -> Merchant BE: GET /api/payments/verify?resourcePath=...
Merchant BE -> HyperPay: GET {baseUrl}{resourcePath}?entityId={entityId}
HyperPay -> Merchant BE: 200 { payment result }
Merchant BE -> Merchant FE: Payment result (success/pending/failed)
Merchant FE -> User Browser: Show confirmation page
Mobile App -> Merchant BE: POST /api/hyperpay/checkouts (amount, currency, brand)
Merchant BE -> HyperPay: POST {baseUrl}/v1/checkouts
HyperPay -> Merchant BE: 200 { id: checkoutId }
Merchant BE -> Mobile App: checkoutId
Mobile App -> HyperPay SDK: startPayment(checkoutId, brands)
HyperPay SDK <-> HyperPay: Card/wallet UI + processing
HyperPay SDK -> Mobile App: onComplete(isSuccess, resourcePath)
Mobile App -> Merchant BE: POST /api/hyperpay/verify { resourcePath, orderId }
Merchant BE -> HyperPay: GET {baseUrl}{resourcePath}?entityId=...
HyperPay -> Merchant BE: 200 { payment result }
Merchant BE -> Mobile App: Payment result

  • Hide HyperPay specifics behind a HyperpayClient and PaymentService interface; expose domain-centric methods like authorizePayment, capturePayment, getPaymentStatus.
  • Keep environment, entityId, and tokens in configuration properties and do not scatter them in business code.
  • Standardize error mapping (e.g., map result.code prefixes into internal enums).
hyperpay:
base-url: https://eu-test.oppwa.com
token: ${HYPERPAY_TOKEN}
mode: test # or live
currency: SAR
entities:
visa-master: ${HYPERPAY_CREDIT_ID}
mada: ${HYPERPAY_MADA_ID}
applepay: ${HYPERPAY_APPLE_ID}
connect:
read-timeout-ms: 5000
connect-timeout-ms: 2000
@Configuration
public class HyperpayClientConfig {
@Bean
public WebClient hyperpayWebClient(@Value("${hyperpay.base-url}") String baseUrl,
@Value("${hyperpay.token}") String token) {
return WebClient.builder()
.baseUrl(baseUrl)
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create()
.responseTimeout(Duration.ofSeconds(5))
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000)))
.build();
}
}
public record CreateCheckoutRequest(
BigDecimal amount,
String currency,
String paymentType, // DB, PA, etc.
String entityId,
String merchantTransactionId,
String customerEmail
) {}
public record CreateCheckoutResponse(
String id, // checkoutId
String resultCode,
String resultDescription
) {}
public record PaymentStatusResponse(
String id,
String resultCode,
String resultDescription,
String paymentBrand,
String descriptor
) {}
public interface HyperpayClient {
Mono<CreateCheckoutResponse> createCheckout(CreateCheckoutRequest request);
Mono<PaymentStatusResponse> getPaymentStatusByCheckoutId(String checkoutId, String entityId);
Mono<PaymentStatusResponse> getPaymentStatusByResourcePath(String resourcePath, String entityId);
}
@Service
public class HyperpayClientImpl implements HyperpayClient {
private final WebClient client;
public HyperpayClientImpl(WebClient hyperpayWebClient) {
this.client = hyperpayWebClient;
}
@Override
public Mono<CreateCheckoutResponse> createCheckout(CreateCheckoutRequest req) {
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.add("entityId", req.entityId());
form.add("amount", req.amount().setScale(2, RoundingMode.HALF_UP).toPlainString());
form.add("currency", req.currency());
form.add("paymentType", req.paymentType());
form.add("merchantTransactionId", req.merchantTransactionId());
if (req.customerEmail() != null) {
form.add("customer.email", req.customerEmail());
}
return client.post()
.uri("/v1/checkouts")
.body(BodyInserters.fromFormData(form))
.retrieve()
.onStatus(HttpStatusCode::isError, this::mapError)
.bodyToMono(JsonNode.class)
.map(json -> new CreateCheckoutResponse(
json.path("id").asText(),
json.path("result").path("code").asText(),
json.path("result").path("description").asText()
));
}
@Override
public Mono<PaymentStatusResponse> getPaymentStatusByCheckoutId(String checkoutId, String entityId) {
String uri = String.format("/v1/checkouts/%s/payment?entityId=%s", checkoutId, entityId);
return doGetStatus(uri);
}
@Override
public Mono<PaymentStatusResponse> getPaymentStatusByResourcePath(String resourcePath, String entityId) {
// resourcePath already starts with /v1/...
String uri = String.format("%s?entityId=%s", resourcePath, entityId);
return doGetStatus(uri);
}
private Mono<PaymentStatusResponse> doGetStatus(String uri) {
return client.get()
.uri(uri)
.retrieve()
.onStatus(HttpStatusCode::isError, this::mapError)
.bodyToMono(JsonNode.class)
.map(json -> new PaymentStatusResponse(
json.path("id").asText(),
json.path("result").path("code").asText(),
json.path("result").path("description").asText(),
json.path("paymentBrand").asText(),
json.path("descriptor").asText()
));
}
private Mono<? extends Throwable> mapError(ClientResponse response) {
return response.bodyToMono(String.class)
.defaultIfEmpty("")
.flatMap(body -> Mono.error(
new HyperpayApiException(
"HyperPay API error: status=%s body=%s".formatted(response.statusCode(), body))
));
}
}
public class HyperpayApiException extends RuntimeException {
public HyperpayApiException(String message) { super(message); }
}
@Service
public class PaymentService {
private final HyperpayClient hyperpayClient;
private final HyperpayProperties props;
public PaymentService(HyperpayClient hyperpayClient, HyperpayProperties props) {
this.hyperpayClient = hyperpayClient;
this.props = props;
}
public Mono<String> createCheckoutForOrder(Order order, PaymentBrand brand) {
String entityId = props.entityIdForBrand(brand);
CreateCheckoutRequest req = new CreateCheckoutRequest(
order.getAmount(),
props.currency(),
"DB", // direct debit (sale)
entityId,
order.getExternalReference(),
order.getCustomer().getEmail()
);
return hyperpayClient.createCheckout(req)
.flatMap(resp -> {
if (!resp.resultCode().startsWith("000.200.")) {
return Mono.error(new IllegalStateException("Failed to create checkout: " + resp.resultDescription()));
}
return Mono.just(resp.id());
});
}
public Mono<PaymentStatus> finalizePayment(String resourcePath, PaymentBrand brand) {
String entityId = props.entityIdForBrand(brand);
return hyperpayClient.getPaymentStatusByResourcePath(resourcePath, entityId)
.map(this::mapToDomainStatus);
}
private PaymentStatus mapToDomainStatus(PaymentStatusResponse resp) {
String code = resp.resultCode();
if (code.startsWith("000.000") || code.startsWith("000.100")) {
return PaymentStatus.SUCCESS;
} else if (code.startsWith("000.200")) {
return PaymentStatus.PENDING;
} else {
return PaymentStatus.FAILED;
}
}
}
  • Wrap all HyperPay calls in domain-specific exceptions (HyperpayApiException, PaymentFailedException).
  • Use retryWhen with backoff for transient HTTP 5xx/timeouts; avoid retrying 4xx/validation errors.
  • Always implement idempotency at the order/payment layer so that retries do not double-charge.

HyperPay/OPP-based gateways support asynchronous notifications either via browser redirects with resourcePath (already shown) and, depending on account configuration, via dedicated webhooks / notification URLs.

  • Configure a global notification URL in the HyperPay/OPP merchant dashboard if available.
  • Use a dedicated HTTPS endpoint, e.g. /api/webhooks/hyperpay, with:
    • POST only.
    • Strict IP allow-list or HMAC-style shared secret.
    • Short timeouts and quick 200 OK response.

Spring Boot webhook controller (resourcePath model)

Section titled “Spring Boot webhook controller (resourcePath model)”
@RestController
@RequestMapping("/api/webhooks/hyperpay")
public class HyperpayWebhookController {
private final PaymentService paymentService;
public HyperpayWebhookController(PaymentService paymentService) {
this.paymentService = paymentService;
}
// For browser redirect: /payment/result?resourcePath=...&id=...
@GetMapping("/result")
public Mono<ResponseEntity<Void>> paymentResult(@RequestParam("resourcePath") String resourcePath,
@RequestParam("brand") String brand) {
PaymentBrand paymentBrand = PaymentBrand.fromExternal(brand);
return paymentService.finalizePayment(resourcePath, paymentBrand)
.doOnNext(status -> {/* update order, emit events, etc. */})
.thenReturn(ResponseEntity.status(HttpStatus.FOUND)
.location(URI.create("/payments/thank-you"))
.build());
}
// For pure webhook-style JSON notifications if enabled later
@PostMapping
public ResponseEntity<Void> webhook(@RequestBody Map<String, Object> payload,
@RequestHeader Map<String, String> headers) {
// Validate signature / shared secret in headers if configured
// Extract resourcePath or payment.id then trigger verification flow
return ResponseEntity.ok().build();
}
}
  • Store a unique checkoutId or paymentId per order and enforce UNIQUE constraint to prevent double-posting.
  • Maintain a payment_events table keyed by HyperPay id / ndc / checkoutId so that processing a notification twice becomes a no-op.
  • Use at-least-once semantics; design logic to be idempotent so that both webhook and frontend-triggered verification can run safely.
  • Pattern across payment gateways:
    • Shared secret in HTTP header (e.g., X-Notification-Secret) or an HMAC over the payload.
  • When HyperPay provides such a secret:
    • Persist it in secure config.
    • On each webhook, recompute HMAC using payload and compare in constant time.
    • Reject any request without valid auth.

OPP-style result codes follow a AAA.BBB.CCC pattern with ranges indicating success, pending, or error.

Typical examples:

  • 000.200.100 – “successfully created checkout” (checkout creation OK).
  • 000.000.xxx / 000.100.xxx – successful payments / successful request.
  • 000.200.xxx – transaction pending / initialized / QR scanned; treat as PENDING.
  • 800.xxx.xxx – various bank/issuer declines (e.g., CVV wrong, insufficient funds, etc.).
public enum PaymentStatus { SUCCESS, PENDING, FAILED; }
public PaymentStatus mapResultCode(String code) {
if (code == null) return PaymentStatus.FAILED;
if (code.startsWith("000.000") || code.startsWith("000.100")) {
return PaymentStatus.SUCCESS;
} else if (code.startsWith("000.200")) {
return PaymentStatus.PENDING;
} else {
return PaymentStatus.FAILED;
}
}
  • SUCCESS: Mark order as paid, persist gateway metadata (paymentBrand, descriptor, masked PAN if provided), emit domain events.
  • PENDING: Show a pending screen; rely on webhook/cron retries to poll status until it reaches a terminal state (SUCCESS or FAILED).
  • FAILED: Surface a retry option; do not automatically create new checkouts using the same checkoutId.
  • For checkout creation (POST /v1/checkouts):

    • Retry on timeouts and 5xx with exponential backoff.
    • Use a client-side idempotency key (e.g., order ID) to ensure only a single order-level payment is in-flight.
  • For payment status (GET ...payment):

    • Safe to retry multiple times; idempotent by design.
  • For user-facing retries (user clicks “Pay” again):

    • First check whether there is already a successful payment for that order.
    • If previous state is PENDING, consider polling before creating a new checkout.

  • Using hosted widgets/SDKs ensures card data goes directly to HyperPay; your backend only sees tokens (checkoutId, paymentId, resourcePath), keeping you closer to SAQ A instead of SAQ D.
  • Direct S2S integrations involving PAN/CVV place your environment in scope for PCI DSS and forbid storing CVV after authorization; CVV should exist only transiently in RAM and be discarded immediately after processing.
  • Any stored PAN must be encrypted at rest and protected with strong key management and multiple authenticators.
  • HyperPay supports registrations endpoints (/v1/registrations and /v1/registrations/{id}/payments) to store cards as tokens and charge them later without handling PAN each time.
  • Your system should store only opaque HyperPay registration IDs and never raw PAN or CVV.
  • Never log PAN, CVV, or full cardholder names; ensure log masking at both application and infrastructure layers.
  • Ensure request dumps and APM traces scrub sensitive fields.
  • Enforce TLS 1.2+ to HyperPay endpoints.
  • For your own payment pages and webhooks, enable:
    • Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options (or CSP frame-ancestors when embedding widgets), and Content-Security-Policy tuned to allow only HyperPay widget origins.

  • Log at INFO level:
    • checkoutId, entityId alias, orderId, mapped PaymentStatus, and high-level result description.
  • Log at DEBUG level:
    • Full HyperPay response JSON with sensitive fields redacted.
  • Emit metrics:
    • Checkout creation latency and error rates.
    • Payment success/failure rates by brand.
    • Webhook processing latency and error rates.
  • Introduce an Idempotency-Key header or internal idempotency token for operations like “create payment for order X” so that retries do not double-charge.
  • Persist idempotency keys with the payment record and enforce uniqueness at the DB level.
  • Outbound HTTP:

    • Connect timeout ≈ 2–3 seconds, read timeout ≈ 5–10 seconds.
    • Limit max total retries to a small number (2–3) for idempotent operations only.
  • Use Resilience4j for:

    • Circuit breaker per HyperPay host (open on repeated 5xx/timeouts).
    • Bulkhead/isolation to prevent payment spikes from exhausting HTTP connection pool.

Example Resilience4j config:

resilience4j.circuitbreaker:
instances:
hyperpay:
sliding-window-type: COUNT_BASED
sliding-window-size: 20
failure-rate-threshold: 50
wait-duration-in-open-state: 30s
permitted-number-of-calls-in-half-open-state: 5
resilience4j.retry:
instances:
hyperpay:
max-attempts: 3
wait-duration: 500ms
retry-exceptions:
- java.io.IOException
- org.springframework.web.reactive.function.client.WebClientRequestException
  • If checkout creation succeeds but persisting the order fails, reconcile using merchantTransactionId or checkoutId search in HyperPay backoffice.
  • If HyperPay confirms payment success but your DB update fails, persist a compensating event or use an outbox pattern; never re-charge the customer on recovery.

  • Use test environment (https://eu-test.oppwa.com) with dedicated test entities and sample card numbers as provided by HyperPay or integration libraries.
  • Some community packages list test cards (e.g., Visa 4111111111111111 with CVV 123 and future expiry) used in sandbox.
  • For unit tests, mock HyperpayClient interface.
  • For integration tests, run a lightweight HTTP server (WireMock/Testcontainers) on localhost that mimics /v1/checkouts and /v1/checkouts/{id}/payment behavior.
  • Record real sandbox responses and replay them during tests.
  • Test matrix:
    • Brands: VISA/MASTER, MADA, STC Pay, Apple Pay.
    • Scenarios: success, issuer decline (800.xxx.xxx), pending, 3DS challenge, user cancel.
  • On mobile, verify that checkoutId is obtained from your server and not directly from HyperPay, and that resourcePath is always passed back to the backend for verification.
  • Network timeouts when creating checkout or checking status.
  • Duplicate notifications (same resourcePath or id processed multiple times).
  • Browser closed before redirect; rely on webhook or scheduled polling.
  • User double-clicks “Pay”; ensure idempotency by order/checkout link.

  • ⚠️ Incorrect entityId usage: Using a VISA/MASTER entityId for MADA or Apple Pay will lead to brand or acquirer failures; maintain a clear mapping of brand → entityId.
  • ⚠️ Mismatched environments: Using test checkoutId with live widget script (or vice versa) causes invalid session errors; make sure base URLs and scripts are consistent (test.oppwa.com vs oppwa.com).
  • ⚠️ Async vs sync confusion: Relying solely on frontend success callbacks without server-side verification of resourcePath can cause false positives; always verify on the backend.
  • ⚠️ Payment status misinterpretation: Treating 000.200.xxx as success instead of pending can cause premature order confirmation; map ranges correctly.
  • ⚠️ Storing CVV or PAN in logs: Violates PCI DSS and increases breach impact; scrub sensitive data aggressively.

Important endpoints (HyperPay / OPP style)

Section titled “Important endpoints (HyperPay / OPP style)”
PurposeMethod & PathNotes
Create checkoutPOST /v1/checkoutsReturns id (checkoutId); result.code=000.200.100 for success.
Payment status by checkoutGET /v1/checkouts/{checkoutId}/payment?entityId=...Returns payment result for a given checkout.
Payment status by resourcePathGET {resourcePath}?entityId=...resourcePath comes from redirect/SDK (e.g. /v1/checkouts/{id}/payment).
Payment widgets script (test)https://test.oppwa.com/v1/paymentWidgets.js?checkoutId={id}Used to render hosted form.
Payment widgets script (live)https://oppwa.com/v1/paymentWidgets.js?checkoutId={id}Production equivalent.
Card registrationsPOST /v1/registrations, POST /v1/registrations/{id}/paymentsCreate token and charge stored card.

Required parameters for POST /v1/checkouts (typical set)

Section titled “Required parameters for POST /v1/checkouts (typical set)”

Common fields seen in production examples (actual required list depends on your merchant configuration):

  • entityId – entity ID for selected brand (MADA vs VISA/MASTER etc.).
  • amount – decimal amount (e.g., 100.00).
  • currency – ISO currency, e.g. SAR.
  • paymentTypeDB (debit/sale) or PA (preauthorization).
  • merchantTransactionId – your idempotent transaction reference.
  • Optional customer/billing fields: customer.email, customer.givenName, billing.city, billing.country, etc.

Sample cURL – create checkout (Bearer token)

Section titled “Sample cURL – create checkout (Bearer token)”
Terminal window
TOKEN="${HYPERPAY_TOKEN}"
BASE_URL="https://eu-test.oppwa.com"
ENTITY_ID="${HYPERPAY_CREDIT_ID}"
curl -X POST "${BASE_URL}/v1/checkouts" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "entityId=${ENTITY_ID}" \
-d "amount=100.00" \
-d "currency=SAR" \
-d "paymentType=DB" \
-d "merchantTransactionId=ORDER-12345"

Example response shape:

{
"buildNumber": "",
"id": "8ac7a4c780affe890180b1df0f4b0e29",
"result": {
"code": "000.200.100",
"description": "successfully created checkout"
},
"ndc": "8ac7a4c780affe890180b1df0f4b0e29",
"script_url": "https://test.oppwa.com/v1/paymentWidgets.js?checkoutId=8ac7a4c780affe890180b1df0f4b0e29"
}

Sample cURL – get payment status by checkoutId

Section titled “Sample cURL – get payment status by checkoutId”
Terminal window
TOKEN="${HYPERPAY_TOKEN}"
BASE_URL="https://eu-test.oppwa.com"
ENTITY_ID="${HYPERPAY_CREDIT_ID}"
CHECKOUT_ID="8ac7a4c780affe890180b1df0f4b0e29"
curl -X GET "${BASE_URL}/v1/checkouts/${CHECKOUT_ID}/payment?entityId=${ENTITY_ID}" \
-H "Authorization: Bearer ${TOKEN}"
CategoryHTTP statusExample result.codeMeaning / handling
Checkout created200000.200.100Checkout created; proceed to widget/SDK.
Payment success200000.000.xxx, 000.100.1xxPayment successful; mark order as paid.
Payment pending200000.200.xxxTransaction pending/initialized; keep polling or wait for webhook.
Bank/issuer decline200800.xxx.xxxDeclined; show error and allow retry with different method.
Client error4xxA000x (in some APIs)Signature/parameter/timestamp errors; fix request.
Server error5xxN/AGateway/internal error; retry with idempotency, circuit breaker.

This cheatsheet intentionally focuses on practical integration details visible across real-world HyperPay/OPP implementations and should be complemented with the latest official merchant documentation and credentials from HyperPay support for production use.