A small, deliberate merchant surface.
The stable v1 merchant API covers hosted PayIn collections and directly authorized PayOut instructions. Your API secret never enters customer browsers, and bank/provider credentials remain behind Experia360.
https://api.experia360.com/api/v1application/jsonINR · amount_paiseURL versioned · /v1PCI boundary: Experia360 merchant APIs do not ask for card numbers, UPI PINs or customer OTPs. Never collect or forward those values.
Two server-side credentials.
Generate a client under Dashboard → APIHub. The secret is displayed once. Store it in a secrets manager or protected server environment.
| Header | Value | Required |
|---|---|---|
X-Experia-Client-Id | exc_… | Every authenticated request |
Authorization | Bearer exs_… | Every authenticated request |
Idempotency-Key | UUID v4 | Every financial create request |
Create payment orders.
Retrieve the authoritative order status.
Authorize and submit a payout directly to the configured provider route.
Retrieve payout status, UTR and attempts.
Network restriction: Configure the public outbound IPv4 or IPv6 addresses of your servers in APIHub. Once at least one address is saved, all other source IPs receive API_IP_NOT_ALLOWED.
Create the first hosted payment.
- 1Activate Experia Collect and APIHub
Complete onboarding and configure an approved collection route.
- 2Create a scoped client
Select
collections:writeandcollections:read. - 3Create a payment order from your backend
Persist your UUID idempotency key against the merchant order.
- 4Follow next_action
Redirect to hosted checkout or render the returned UPI intent without altering its payload.
- 5Confirm server-side
Verify the signed callback and retrieve the order before fulfilment.
/merchant/payment-orderscollections:writeCreate payment order
Creates one amount-bound payment order. Use CHECKOUT for the complete Experia360-hosted page or INTENT to receive an app-ready UPI URI and QR payload. Repeating the same idempotency key returns the original order.
response_mode: CHECKOUTExperia360 collects payer details, presents the dynamic QR, tracks expiry and shows the final payment state.
response_mode: INTENTYour application renders the exact returned UPI URI. Customer details and explicit consent are mandatory.
| Field | Type | Rule |
|---|---|---|
amount_paise | integer | Required · 100 to 10,000,000,000 |
purpose | string | Required · 3–255 characters |
merchant_order_reference | string | Optional · max 120 |
response_mode | enum | CHECKOUT (default) or INTENT |
customer_name | string | Required for INTENT · max 120 |
customer_email | Required for INTENT · max 190 | |
customer_phone | string | Required for INTENT · Indian 10-digit mobile |
customer_consent | boolean | Must be true for INTENT |
expires_in_minutes | integer | Optional · 5–1,440; QR begins after payer details |
callback_url | HTTPS URL | Optional; required with callback_secret |
callback_secret | string | Optional · 32–500; required with callback_url |
return_url | HTTPS URL | Optional; browser convenience only |
curl --request POST \
--url https://api.experia360.com/api/v1/merchant/payment-orders \
--header 'Authorization: Bearer exs_your_secret' \
--header 'X-Experia-Client-Id: exc_your_client_id' \
--header 'Idempotency-Key: 72566cd5-39bf-4f58-92a8-66a993e74f17' \
--header 'Content-Type: application/json' \
--data '{
"amount_paise": 125000,
"purpose": "Invoice INV-1042",
"merchant_order_reference": "ORDER-1042",
"response_mode": "CHECKOUT",
"customer_name": "Aarav Mehta",
"customer_email": "aarav@example.com",
"customer_phone": "9876543210",
"expires_in_minutes": 15,
"callback_url": "https://merchant.example/webhooks/experia360",
"callback_secret": "use-at-least-32-random-characters",
"return_url": "https://merchant.example/orders/ORDER-1042"
}'{
"success": true,
"data": {
"id": "019...",
"order_id": "EUPI...",
"merchant_order_reference": "ORDER-1042",
"amount_paise": 125000,
"currency": "INR",
"status": "AWAITING_CUSTOMER",
"response_mode": "CHECKOUT",
"verification_mode": "TREXO_PAYIN_STATUS_API",
"next_action": {
"type": "HOSTED_CHECKOUT",
"url": "https://experia360.com/pay/..."
},
"checkout_url": "https://experia360.com/pay/..."
},
"request_id": "..."
}Direct intent bodyOpen example
{
"amount_paise": 125000,
"purpose": "Invoice INV-1042",
"merchant_order_reference": "ORDER-1042",
"response_mode": "INTENT",
"customer_name": "Aarav Mehta",
"customer_email": "aarav@example.com",
"customer_phone": "9876543210",
"customer_consent": true,
"expires_in_minutes": 15
}Direct intent responseOpen example
{
"success": true,
"data": {
"order_id": "EUPI...",
"status": "PENDING_PAYMENT",
"response_mode": "INTENT",
"next_action": {
"type": "UPI_INTENT",
"upi_uri": "upi://pay?pa=...&am=1250.00&cu=INR&tr=EUPI...",
"qr_payload": "upi://pay?pa=...&am=1250.00&cu=INR&tr=EUPI...",
"expires_at": "2026-10-08T13:00:00Z"
},
"upi_intent": "upi://pay?pa=...&am=1250.00&cu=INR&tr=EUPI...",
"checkout_url": "https://experia360.com/pay/..."
},
"request_id": "..."
}Do not mark an order paid from a redirect. Fulfil only after a valid signed collection.received callback or an authenticated status response reports status: PAID and provider_status: SUCCESS. Provider callbacks are acknowledged before merchant delivery, so a slow merchant endpoint cannot delay provider processing.
/merchant/payment-orders/{order}collections:readRetrieve payment order
Use the Experia360 UUID or order_id. For an open Trexo or PayOnTop payment, this endpoint performs a lock-protected live provider check when the previous check is older than two seconds, then returns the authoritative payment state, provider status, UTR and confirmation time. Browser return parameters never establish settlement.
curl --request GET \
--url https://api.experia360.com/api/v1/merchant/payment-orders/EUPI... \
--header 'Authorization: Bearer exs_your_secret' \
--header 'X-Experia-Client-Id: exc_your_client_id'/merchant/payoutspayouts:writeCreate payout instruction
Creates an idempotent payout and submits it directly to the configured provider route. The authenticated client with payouts:write is treated as the merchant's server-side authorization, so no secondary dashboard approval is requested. Supply either an existing beneficiary_id or beneficiary bank details inline. Inline details are IFSC-checked and stored as a reusable confirmed beneficiary.
| Field | Type | Rule |
|---|---|---|
beneficiary_id | UUID | Required when beneficiary is omitted · active confirmed beneficiary |
beneficiary | object | Required when beneficiary_id is omitted |
beneficiary.name | string | Required inline · max 160 |
beneficiary.account_number | string | Required inline · 8–20 digits |
beneficiary.ifsc | string | Required inline · valid 11-character Indian IFSC |
beneficiary.mobile | string | Required inline · Indian 10-digit mobile |
beneficiary.details_confirmation | boolean | Must be true for inline details |
bank_connection_id | UUID | Optional · selected active payout route |
amount_paise | integer | Required · 100 to 10,000,000,000 |
mode | enum | IMPS, NEFT or RTGS as enabled on the route |
client_reference | string | Optional · max 100 |
remarks | string | Optional · max 255 |
curl --request POST \
--url https://api.experia360.com/api/v1/merchant/payouts \
--header 'Authorization: Bearer exs_your_secret' \
--header 'X-Experia-Client-Id: exc_your_client_id' \
--header 'Idempotency-Key: a90f9328-c328-4f32-92dc-668cc14fd9d4' \
--header 'Content-Type: application/json' \
--data '{
"beneficiary": {
"name": "Aarav Mehta",
"account_number": "50100123456789",
"ifsc": "HDFC0001234",
"mobile": "9876543210",
"details_confirmation": true
},
"amount_paise": 250000,
"mode": "IMPS",
"client_reference": "VENDOR-INV-2041",
"remarks": "Approved vendor invoice"
}'{
"success": true,
"data": {
"id": "019...",
"transaction_id": "EPO1234567890",
"client_reference": "VENDOR-INV-2041",
"amount_paise": 250000,
"currency": "INR",
"mode": "IMPS",
"status": "PROCESSING",
"beneficiary": {
"id": "019...",
"name": "Aarav Mehta",
"account_number_last4": "6789",
"ifsc": "HDFC0001234",
"bank_name": "HDFC Bank"
},
"attempts": [{
"status": "PROCESSING",
"bank_reference": "POT-API-001",
"utr": null
}]
},
"approval_required": false,
"message": "The payout instruction was submitted directly to the configured provider route.",
"request_id": "..."
}Experia360 never returns the full destination account number in responses or callbacks. Merchants receive the beneficiary ID, masked last four digits, IFSC, bank name, UTR and bank reference.
A successful create response means Experia360 submitted the instruction directly. Funds are final only when the authenticated status response or signed callback reports SUCCESS and provides the provider reference or UTR. Dashboard, bulk and manually created payouts continue to use maker-checker approval.
/merchant/payouts/{payout}payouts:readRetrieve payout status
Use the Experia360 payout UUID or transaction ID. The response includes provider attempts, bank reference and UTR when available and remains scoped to the API client that created it.
curl --request GET \
--url https://api.experia360.com/api/v1/merchant/payouts/EPO1234567890 \
--header 'Authorization: Bearer exs_your_secret' \
--header 'X-Experia-Client-Id: exc_your_client_id'Make every outcome explicit.
AWAITING_CUSTOMERHosted checkout is waiting for verified payer details.
PENDING_PAYMENTA dynamic UPI intent is active and provider confirmation is pending.
AWAITING_CONFIRMATIONA payer reference was supplied; bank/provider confirmation is pending.
PAIDPayment is confirmed and contains a bank transaction reference.
MANUALLY_CONFIRMEDAn authorized operations review confirmed payment evidence.
FAILEDThe provider returned a terminal failure; create a new order to retry.
EXPIREDThe payment window ended without confirmation.
CANCELLEDThe merchant cancelled an open request.
REFUNDEDThe confirmed collection was fully refunded after review and processing.
Verify the exact raw body.
APIHub supports separate PayIn and PayOut callback URLs. Each destination receives its own signing secret when first configured. Verify timestamp + "." + rawBody against the exact request body.
These are Experia360-to-merchant callbacks. Do not give merchants the Trexo-to-Experia360 provider callback URL. Configure the merchant's public HTTPS PayIn and PayOut destinations separately in APIHub.
| Header | Purpose |
|---|---|
X-Experia-Event-Id | Replay-protection identifier |
X-Experia-Timestamp | Unix timestamp; reject stale delivery |
X-Experia-Signature | v1= followed by lowercase HMAC-SHA256 |
| Destination | Events | When to act |
|---|---|---|
| PayIn callback | collection.received, collection.refund.requested, collection.refund.completed | Fulfil only on a valid collection.received event with status: PAID. |
| PayOut callback | payout.created, payout.success, payout.failed | Treat only payout.success as a completed bank transfer; retain its UTR. |
{
"event": "collection.received",
"event_id": "6be7b76e-...",
"created_at": "2026-09-25T04:30:00Z",
"data": {
"order_id": "EUPI...",
"merchant_order_reference": "ORDER-1042",
"status": "PAID",
"provider_status": "SUCCESS",
"amount_paise": 125000,
"currency": "INR",
"bank_transaction_id": "RRN123456789012",
"confirmed_at": "2026-09-25T04:29:58Z"
}
}Test a PayIn callback
Use the PayIn destination and its own one-time signing secret. A confirmed collection is delivered as collection.received.
PayIn callback cURLOpen example
CALLBACK_URL="https://merchant.example.com/webhooks/experia360/payin"
CALLBACK_SECRET="whsec_replace_with_payin_signing_secret"
TIMESTAMP="$(date +%s)"
EVENT_ID="payin-test-$(date +%s)"
BODY='{
"event": "collection.received",
"event_id": "'"${EVENT_ID}"'",
"created_at": "2026-10-09T10:30:00Z",
"data": {
"order_id": "EUPI1234567890",
"merchant_order_reference": "ORDER-1042",
"amount_paise": 125000,
"currency": "INR",
"status": "PAID",
"bank_transaction_id": "615276353375",
"confirmed_at": "2026-10-09T10:29:58Z"
}
}'
SIGNATURE="v1=$(printf '%s' "${TIMESTAMP}.${BODY}" |
openssl dgst -sha256 -hmac "${CALLBACK_SECRET}" -hex |
sed 's/^.* //')"
curl --request POST \
--url "${CALLBACK_URL}" \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header "X-Experia-Event-Id: ${EVENT_ID}" \
--header "X-Experia-Timestamp: ${TIMESTAMP}" \
--header "X-Experia-Signature: ${SIGNATURE}" \
--data-binary "${BODY}"Test a PayOut callback
Use the separate PayOut destination and signing secret. Replace payout.success with payout.created or payout.failed when testing those lifecycle events.
PayOut callback cURLOpen example
CALLBACK_URL="https://merchant.example.com/webhooks/experia360/payout"
CALLBACK_SECRET="whsec_replace_with_payout_signing_secret"
TIMESTAMP="$(date +%s)"
EVENT_ID="payout-test-$(date +%s)"
BODY='{
"event": "payout.success",
"event_id": "'"${EVENT_ID}"'",
"created_at": "2026-10-09T10:35:00Z",
"data": {
"payout_id": "01912345-6789-7000-8000-123456789abc",
"transaction_id": "EPO1234567890",
"client_reference": "VENDOR-INV-2041",
"amount_paise": 250000,
"currency": "INR",
"mode": "IMPS",
"status": "SUCCESS",
"beneficiary": {
"id": "01912345-6789-7000-8000-abcdef123456",
"name": "Aarav Mehta",
"account_number_last4": "6789",
"ifsc": "HDFC0001234",
"bank_name": "HDFC Bank"
},
"bank_reference": "BANKREF123456",
"utr": "615276353375",
"processed_at": "2026-10-09T10:34:58Z"
}
}'
SIGNATURE="v1=$(printf '%s' "${TIMESTAMP}.${BODY}" |
openssl dgst -sha256 -hmac "${CALLBACK_SECRET}" -hex |
sed 's/^.* //')"
curl --request POST \
--url "${CALLBACK_URL}" \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header "X-Experia-Event-Id: ${EVENT_ID}" \
--header "X-Experia-Timestamp: ${TIMESTAMP}" \
--header "X-Experia-Signature: ${SIGNATURE}" \
--data-binary "${BODY}"$timestamp = $_SERVER['HTTP_X_EXPERIA_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_EXPERIA_SIGNATURE'] ?? '';
$rawBody = file_get_contents('php://input');
if (!ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {
http_response_code(401); exit;
}
$expected = 'v1=' . hash_hmac(
'sha256',
$timestamp . '.' . $rawBody,
getenv('EXPERIA360_CALLBACK_SECRET')
);
if (!hash_equals($expected, $signature)) {
http_response_code(401); exit;
}Return HTTP 2xx promptly. Use constant-time comparison, reject events older than five minutes, store processed event IDs, and retrieve the payment order before fulfilment.
Retry only when it is safe.
Do not retry unchanged validation failures.
Rotate compromised secrets; never log them.
Resources are organization-scoped.
Respect the configured client rate limit.
Never generate a new idempotency key for an uncertain create.
{
"success": false,
"error": {
"code": "API_SCOPE_DENIED",
"message": "The API client is not permitted to perform this operation.",
"request_id": "..."
}
}Install the supported gateway.
Version 1.1.0 creates orders server-side, redirects customers to the hosted QR checkout, verifies signed callbacks, and performs bounded authenticated status reconciliation.
- Classic checkout and Checkout Blocks
- WooCommerce HPOS declared compatible
- AES-256-GCM encrypted saved secrets
- Replay and amount mismatch protection
- PHP 7.4+ and WordPress 6.4+
- 1Upload and activatePlugins → Add New → Upload Plugin.
- 2Create API clientAssign collection read and write scopes.
- 3Configure WooCommerceSettings → Payments → Experia360 UPI.
- 4Run a controlled orderValidate QR, callback and order reconciliation.