Overview
EpicPay allows merchants to create payment sessions from their backend, redirect customers to a secure hosted checkout page, receive payment status updates through webhooks, and verify payment status through API.
Secure payment API documentation for merchants and developers. Create payment sessions, redirect customers to checkout, verify payments, and receive webhook notifications.
EpicPay allows merchants to create payment sessions from their backend, redirect customers to a secure hosted checkout page, receive payment status updates through webhooks, and verify payment status through API.
Use this environment for development and testing only.
https://epicstore-key.com/wp-json/epicpay/v1Use this environment only after your merchant account is approved for production.
https://epicstore-key.com/wp-json/epicpay/v1The customer clicks Pay Now on the merchant website.
The merchant backend creates a payment session through EpicPay API.
EpicPay returns a secure payment_url.
The merchant redirects the customer to the EpicPay checkout page.
The customer completes or cancels the payment.
EpicPay redirects the customer back to the merchant website.
EpicPay sends a webhook to the merchant backend.
The merchant verifies the payment status and updates the order.
Every server-side API request must include your merchant API credentials.
Content-Type: application/json
X-EPICPAY-KEY: epk_live_xxxxxxxxxxxxxxxxx
X-EPICPAY-SECRET: eps_live_xxxxxxxxxxxxxxxxx| Key | Description | Visibility |
|---|---|---|
| Public Key | Used to identify the merchant. | Server-side recommended. |
| Secret Key | Used to authenticate API requests. | Server-side only. Never expose publicly. |
| Webhook Secret | Used to verify webhook signatures. | Server-side only. |
X-EPICPAY-SECRET in frontend code.order_id for each order.Creates a new payment session and returns a hosted checkout URL.
/payments/createPOST https://epicstore-key.com/wp-json/epicpay/v1/payments/createContent-Type: application/json
X-EPICPAY-KEY: epk_test_xxxxxxxxxxxxxxxxx
X-EPICPAY-SECRET: eps_test_xxxxxxxxxxxxxxxxx{
"amount": 25000,
"currency": "IQD",
"order_id": "ORDER-1001",
"customer_name": "Ahmed Ali",
"customer_email": "[email protected]",
"customer_phone": "+9647700000000",
"description": "Order payment for ORDER-1001",
"return_url": "https://merchant-site.com/payment/success",
"cancel_url": "https://merchant-site.com/payment/cancel",
"webhook_url": "https://merchant-site.com/api/epicpay/webhook"
}| Field | Type | Required | Description |
|---|---|---|---|
| amount | number | Yes | Payment amount. For IQD, use the full amount, e.g. 25000. |
| currency | string | Yes | Currency code, e.g. IQD or USD. |
| order_id | string | Yes | Unique order reference from merchant system. |
| customer_name | string | Recommended | Customer full name. |
| customer_email | string | Optional | Customer email address. |
| customer_phone | string | Recommended | Customer phone number. |
| description | string | Optional | Payment description. |
| return_url | string | Yes | URL where customer returns after payment. |
| cancel_url | string | Yes | URL where customer returns if payment is cancelled. |
| webhook_url | string | Recommended | Merchant backend endpoint that receives payment status updates. |
{
"success": true,
"payment_id": "pay_9f8a7b6c5d4e",
"order_id": "ORDER-1001",
"amount": 25000,
"currency": "IQD",
"status": "pending",
"payment_url": "https://epicstore-key.com/epicpay/checkout/pay_9f8a7b6c5d4e",
"expires_at": "2026-05-27T12:30:00+03:00"
}{
"success": false,
"error": {
"code": "invalid_amount",
"message": "The payment amount is invalid."
}
}Use this endpoint to confirm the payment status from your backend.
/payments/verify/{payment_id}GET https://epicstore-key.com/wp-json/epicpay/v1/payments/verify/pay_9f8a7b6c5d4eX-EPICPAY-KEY: epk_test_xxxxxxxxxxxxxxxxx
X-EPICPAY-SECRET: eps_test_xxxxxxxxxxxxxxxxx{
"success": true,
"payment_id": "pay_9f8a7b6c5d4e",
"order_id": "ORDER-1001",
"amount": 25000,
"currency": "IQD",
"status": "paid",
"paid_at": "2026-05-27T12:20:00+03:00"
}| Status | Meaning |
|---|---|
| pending | Payment session created, waiting for customer payment. |
| paid | Payment completed successfully. |
| failed | Payment failed. |
| cancelled | Customer cancelled the payment. |
| expired | Payment session expired. |
| refunded | Payment was refunded. |
EpicPay sends webhooks to your backend when payment status changes. Webhooks are the recommended way to update order status automatically.
https://merchant-site.com/api/epicpay/webhookContent-Type: application/json
X-EPICPAY-SIGNATURE: sha256=xxxxxxxxxxxxxxxxxxxxxxxx
X-EPICPAY-EVENT: payment.paid
X-EPICPAY-TIMESTAMP: 1779870000{
"event": "payment.paid",
"payment_id": "pay_9f8a7b6c5d4e",
"order_id": "ORDER-1001",
"amount": 25000,
"currency": "IQD",
"status": "paid",
"paid_at": "2026-05-27T12:20:00+03:00"
}| Event | Description |
|---|---|
| payment.created | Payment session was created. |
| payment.paid | Payment completed successfully. |
| payment.failed | Payment failed. |
| payment.cancelled | Payment was cancelled. |
| payment.expired | Payment session expired. |
| payment.refunded | Payment was refunded. |
The recommended signature format is:
HMAC_SHA256(timestamp + "." + raw_body, webhook_secret)The final header value should be:
sha256=generated_hashimport crypto from "crypto";
function verifyEpicPayWebhook(rawBody, signatureHeader, timestamp, webhookSecret) {
const payload = `${timestamp}.${rawBody}`;
const expectedSignature =
"sha256=" +
crypto.createHmac("sha256", webhookSecret).update(payload).digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expectedSignature)
);
}Create .env.local:
EPICPAY_API_URL=https://epicstore-key.com/wp-json/epicpay/v1
EPICPAY_PUBLIC_KEY=epk_test_xxxxxxxxxxxxxxxxx
EPICPAY_SECRET_KEY=eps_test_xxxxxxxxxxxxxxxxx
EPICPAY_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxNEXT_PUBLIC_ for secret values.File: app/api/epicpay/create-payment/route.ts
import { NextResponse } from "next/server";
export async function POST(req: Request) {
try {
const body = await req.json();
const response = await fetch(`${process.env.EPICPAY_API_URL}/payments/create`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-EPICPAY-KEY": process.env.EPICPAY_PUBLIC_KEY || "",
"X-EPICPAY-SECRET": process.env.EPICPAY_SECRET_KEY || ""
},
body: JSON.stringify({
amount: body.amount,
currency: body.currency || "IQD",
order_id: body.order_id,
customer_name: body.customer_name,
customer_email: body.customer_email,
customer_phone: body.customer_phone,
description: body.description,
return_url: `${body.site_url}/payment/success`,
cancel_url: `${body.site_url}/payment/cancel`,
webhook_url: `${body.site_url}/api/epicpay/webhook`
})
});
const data = await response.json();
if (!response.ok || !data.success) {
return NextResponse.json(
{ success: false, error: data.error || "Payment creation failed" },
{ status: response.status || 400 }
);
}
return NextResponse.json(data);
} catch (error) {
return NextResponse.json(
{ success: false, error: "Internal server error" },
{ status: 500 }
);
}
}"use client";
export default function PayButton() {
async function handlePayment() {
const response = await fetch("/api/epicpay/create-payment", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
amount: 25000,
currency: "IQD",
order_id: "ORDER-1001",
customer_name: "Ahmed Ali",
customer_email: "[email protected]",
customer_phone: "+9647700000000",
description: "Order payment",
site_url: window.location.origin
})
});
const data = await response.json();
if (data.success && data.payment_url) {
window.location.href = data.payment_url;
} else {
alert("Payment could not be started.");
}
}
return <button onClick={handlePayment}>Pay with EpicPay</button>;
}File: app/api/epicpay/webhook/route.ts
import { NextResponse } from "next/server";
import crypto from "crypto";
function verifySignature(rawBody: string, signature: string, timestamp: string) {
const secret = process.env.EPICPAY_WEBHOOK_SECRET || "";
const payload = `${timestamp}.${rawBody}`;
const expected =
"sha256=" + crypto.createHmac("sha256", secret).update(payload).digest("hex");
try {
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
} catch {
return false;
}
}
export async function POST(req: Request) {
const rawBody = await req.text();
const signature = req.headers.get("x-epicpay-signature") || "";
const timestamp = req.headers.get("x-epicpay-timestamp") || "";
if (!verifySignature(rawBody, signature, timestamp)) {
return NextResponse.json({ success: false, error: "Invalid signature" }, { status: 401 });
}
const payload = JSON.parse(rawBody);
if (payload.event === "payment.paid" && payload.status === "paid") {
// Update merchant order status as paid using payload.order_id
}
return NextResponse.json({ received: true });
}curl -X POST "https://epicstore-key.com/wp-json/epicpay/v1/payments/create" \
-H "Content-Type: application/json" \
-H "X-EPICPAY-KEY: epk_test_xxxxxxxxxxxxxxxxx" \
-H "X-EPICPAY-SECRET: eps_test_xxxxxxxxxxxxxxxxx" \
-d '{
"amount": 25000,
"currency": "IQD",
"order_id": "ORDER-1001",
"customer_name": "Ahmed Ali",
"customer_email": "[email protected]",
"customer_phone": "+9647700000000",
"description": "Order payment",
"return_url": "https://merchant-site.com/payment/success",
"cancel_url": "https://merchant-site.com/payment/cancel",
"webhook_url": "https://merchant-site.com/api/epicpay/webhook"
}'curl -X GET "https://epicstore-key.com/wp-json/epicpay/v1/payments/verify/pay_9f8a7b6c5d4e" \
-H "X-EPICPAY-KEY: epk_test_xxxxxxxxxxxxxxxxx" \
-H "X-EPICPAY-SECRET: eps_test_xxxxxxxxxxxxxxxxx"You can add an API Test page for merchants inside the dashboard.
| Field | Type | Required |
|---|---|---|
| API URL | text | Yes |
| Public Key | text | Yes |
| Secret Key | password | Yes |
| Amount | number | Yes |
| Currency | select | Yes |
| Order ID | text | Yes |
| Customer Name | text | Optional |
| Customer Email | Optional | |
| Customer Phone | text | Optional |
| Return URL | url | Yes |
| Cancel URL | url | Yes |
| Webhook URL | url | Optional |
Check if merchant keys are valid.
Create a payment session in test mode.
Open the hosted checkout page.
Verify payment status by payment ID.
Send a simulated webhook event.
Display last request and response safely.
eps_test_************abcd.| Code | Meaning |
|---|---|
| invalid_api_key | Public key is invalid. |
| invalid_secret_key | Secret key is invalid. |
| unauthorized_merchant | Merchant is not allowed to use API. |
| inactive_merchant | Merchant account is inactive. |
| invalid_amount | Amount is missing or invalid. |
| invalid_currency | Currency is unsupported. |
| duplicate_order_id | This order ID already exists. |
| invalid_return_url | Return URL is invalid. |
| invalid_cancel_url | Cancel URL is invalid. |
| invalid_webhook_url | Webhook URL is invalid. |
| payment_not_found | Payment ID was not found. |
| payment_expired | Payment session has expired. |
| internal_error | Unexpected server error. |
| HTTP Code | Usage |
|---|---|
| 200 | Request completed successfully. |
| 201 | Payment session created. |
| 400 | Invalid request data. |
| 401 | Authentication failed. |
| 403 | Merchant is not allowed to perform this action. |
| 404 | Payment not found. |
| 409 | Duplicate order ID or conflict. |
| 422 | Validation error. |
| 500 | Server error. |