Payment API
Payment APIs is the standard integration for most merchants. Your backend creates a payment intent, MoneyHash handles routing, retries, and provider communication, and a client-side layer - Embedded or SDK - renders the checkout to your customer.
Prerequisites
Before starting your integration:
- Access your MoneyHash Organization and Account from the dashboard
- Connect your payment providers to the account
- Configure your Payment Defaults and Flow
- Retrieve your API keys from the dashboard
Authentication: Include your API key in the Authorization: Token header on every request.
Base URL: https://web.moneyhash.io/api/v1.4/
How it works
Payment APIs follow a two-layer model. Your backend creates a payment intent and receives an embed_url in return. You pass that URL to your frontend, where the client-side layer - Embedded or SDK - takes over and handles the full checkout experience: method selection, card entry, 3DS, and provider communication. Your backend only needs to listen for webhooks to confirm the outcome.
Step 1 - Create a payment intent
Create an intent on your backend on checkout initiation - never from the client side. API Reference
Required parameters
| Parameter | Type | Description |
|---|---|---|
amount | string | The amount to charge. Max 14 digits, max 2 decimal places. |
amount_currency | string | ISO 4217 currency code e.g. AED, USD, SAR. |
webhook_url | string | Your backend endpoint that receives payment event notifications. |
operation | string | purchase for immediate charge · authorize for auth-only. Required if flow_id is not provided. |
Recommended parameters
| Parameter | Purpose |
|---|---|
billing_data | Customer billing details passed to the provider automatically |
customer | Associate with an existing MoneyHash customer ID |
card_token | Pay with a saved token directly |
allow_tokenize_card | Set to true to allow saving the card for future use |
payment_method | Pre-select a payment method to show on checkout |
payment_type | Set to recurring for non-present cardholder payments |
merchant_initiated | Set to true for merchant-initiated transactions |
threeds.enabled | Set to true to enable 3DS authentication |
expires_after_seconds | Number of seconds after which the intent expires |
successful_redirect_url | Redirect after successful payment |
failed_redirect_url | Redirect after failed payment |
pending_external_action_redirect_url | Redirect when external action is pending |
processed_redirect_url | Redirect after intent is processed |
time_expired_redirect_url | Redirect when intent expires |
closed_redirect_url | Redirect when intent is closed |
Example request
curl --request POST \
--url https://web.moneyhash.io/api/v1.4/payments/intent/ \
--header 'Authorization: Token <your_api_key>' \
--header 'Content-Type: application/json' \
--data '{
"amount": "150",
"amount_currency": "AED",
"operation": "purchase",
"webhook_url": "https://yourbackend.com/webhooks/moneyhash"
}'const response = await fetch('https://web.moneyhash.io/api/v1.4/payments/intent/', {
method: 'POST',
headers: {
'Authorization': 'Token <your_api_key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: '150',
amount_currency: 'AED',
operation: 'purchase',
webhook_url: 'https://yourbackend.com/webhooks/moneyhash'
})
});
const data = await response.json();import requests
response = requests.post(
'https://web.moneyhash.io/api/v1.4/payments/intent/',
headers={
'Authorization': 'Token <your_api_key>',
'Content-Type': 'application/json'
},
json={
'amount': '150',
'amount_currency': 'AED',
'operation': 'purchase',
'webhook_url': 'https://yourbackend.com/webhooks/moneyhash'
}
)
data = response.json()$response = file_get_contents('https://web.moneyhash.io/api/v1.4/payments/intent/', false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Authorization: Token <your_api_key>\r\nContent-Type: application/json",
'content' => json_encode([
'amount' => '150',
'amount_currency' => 'AED',
'operation' => 'purchase',
'webhook_url' => 'https://yourbackend.com/webhooks/moneyhash'
])
]
]));
$data = json_decode($response, true);Example response
{
"status": {
"code": 200,
"message": "success",
"errors": []
},
"data": {
"embed_url": "https://embed.moneyhash.io/embed/payment/9zGrQQa?mh_intent_secret=015789c26c37daee17f4",
"intent_secret": "015789c26c37daee17f4",
"id": "9zGrQQa",
"status": "UNPROCESSED",
"amount": 150,
"amount_currency": "AED",
"type": "Payin",
"account": "jLYko7Z",
"custom_fields": null,
"billing_data": {
"first_name": "",
"last_name": "",
"email": "",
"phone_number": ""
},
"transaction_provider_fields": {},
"active_transaction": null,
"transactions_history": [],
"flow": null,
"flow_data": null,
"is_live": true,
"created": "2026-07-09T10:07:37.065641Z",
"template": null,
"merchant_reference": null,
"customer": null,
"state": "INTENT_FORM",
"state_details": {
"embed_url": "https://embed.moneyhash.io/embed/payment/9zGrQQa?mh_intent_secret=015789c26c37daee17f4"
},
"customer_last_used_payment_method": null,
"last_used_method": null,
"payment_status": {
"status": "NO_AUTHORIZE_ATTEMPTS",
"balances": {
"total_authorized": "0.00",
"total_voided": "0.00",
"available_to_void": "0.00",
"total_captured": "0.00",
"available_to_capture": "0.00",
"total_refunded": "0.00",
"available_to_refund": "0.00"
}
}
},
"count": 1,
"next": null,
"previous": null
}Retain data.id as your intent_id and data.embed_url for the next step.
Include the Idempotency-Key header on intent creation to safely retry requests without creating duplicate intents. Use a unique value per order — your internal order ID works well. See API Idempotency for details.
Step 2 - Render the checkout
Use the embed_url from the intent response to render the MoneyHash checkout to your customer. Choose between Embedded Experience or SDK based on your customization needs.
Embedded Experience
The fastest path - drop in an iframe or redirect directly to the embed_url. MoneyHash renders the full checkout UI including method selection, card entry, 3DS, and provider communication.
<iframe
src="https://embed.moneyhash.io/embed/payment/9zGrQQa?mh_intent_secret=015789c26c37daee17f4"
width="100%"
height="600px"
frameborder="0">
</iframe>→ Embedded Experience - customization options and configuration
SDK
Use the SDK when you need full control over the checkout UI - custom card forms, native pay buttons, your own method selection, and branded screens.
To define where the customer lands after payment, pass successful_redirect_url, failed_redirect_url, and processed_redirect_url at intent creation. See Redirects for details.
Step 3 - Handle webhooks and fulfill the order
MoneyHash sends webhooks to your webhook_url as the payment progresses. For a basic payment flow, there are two webhooks to act on.
Webhook 1 - Transaction successful
This webhook fires when a payment attempt succeeds. Act on this to fulfill the order.
type: transaction.purchase.successful
| Field | Expected value | Purpose |
|---|---|---|
type | transaction.purchase.successful | Confirms the event type |
intent.payment_status.status | CAPTURED | Confirms money was collected |
intent.id | Your order's intent ID | Links the webhook to your order |
{
"type": "transaction.purchase.successful",
"intent": {
"id": "9zGrQQa",
"payment_status": {
"status": "CAPTURED",
"balances": {
"total_captured": "150.00",
"total_authorized": "150.00",
"available_to_refund": "150.00"
}
}
},
"transaction": {
"id": "87ce3591-5e9a-4b87-92c6-c72beee863af",
"status": "purchase.successful"
}
}Webhook 2 - Intent processed
This webhook fires when the intent reaches a terminal state. It confirms no further transactions will be created.
type: intent.processed
| Field | Expected value | Purpose |
|---|---|---|
type | intent.processed | Confirms the event type |
data.intent.status | PROCESSED | Confirms intent is terminal |
data.intent.payment_status.status | CAPTURED | Confirms final monetary state |
{
"type": "intent.processed",
"data": {
"intent": {
"id": "9zGrQQa",
"status": "PROCESSED",
"payment_status": {
"status": "CAPTURED"
},
"transactions_count": 1
}
}
}Key rules for webhook handling
On order fulfillment: Always wait for transaction.purchase.successful with payment_status.status: CAPTURED before releasing an order. Do not act on intermediate webhooks like transaction.purchase.pending_authentication.
On failures: A transaction.purchase.failed webhook does not mean the intent is done. Check intent.status - if it is still UNPROCESSED, MoneyHash may automatically retry on another provider. Only mark an order as permanently failed when the intent reaches CLOSED or EXPIRED without a successful transaction.
On idempotency: Your webhook handler should be idempotent. MoneyHash retries delivery until it receives a successful acknowledgment - you may receive the same event more than once. Use status_id or operation_id to deduplicate.
On source of truth: Always use payment_status.status combined with operations[].latest_status.value as your source of truth - not active_transaction.status, which can be misleading after 3DS flows.
For the full webhook reference - event types, signatures, and retry behavior - see Webhooks.
Paying with a saved card token
If the customer has a previously saved card token, use the charge card token endpoint to create an intent and process the payment in a single call - no separate intent creation needed. The amount, currency, and webhook URL are passed directly to this endpoint. API Reference
The same webhooks apply - listen for transaction.purchase.successful with payment_status.status: CAPTURED to confirm the payment, followed by intent.processed as the terminal signal.
curl --request POST \
--url https://web.moneyhash.io/api/v1.4/payments/charge-card-token/ \
--header 'Authorization: Token <your_api_key>' \
--header 'Content-Type: application/json' \
--data '{
"amount": "150",
"amount_currency": "AED",
"operation": "purchase",
"webhook_url": "https://yourbackend.com/webhooks/moneyhash",
"card_token": "<hashid>"
}'const response = await fetch('https://web.moneyhash.io/api/v1.4/payments/charge-card-token/', {
method: 'POST',
headers: {
'Authorization': 'Token <your_api_key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
amount: '150',
amount_currency: 'AED',
operation: 'purchase',
webhook_url: 'https://yourbackend.com/webhooks/moneyhash',
card_token: '<hashid>'
})
});
const data = await response.json();import requests
response = requests.post(
'https://web.moneyhash.io/api/v1.4/payments/charge-card-token/',
headers={
'Authorization': 'Token <your_api_key>',
'Content-Type': 'application/json'
},
json={
'amount': '150',
'amount_currency': 'AED',
'operation': 'purchase',
'webhook_url': 'https://yourbackend.com/webhooks/moneyhash',
'card_token': '<hashid>'
}
)
data = response.json()$response = file_get_contents('https://web.moneyhash.io/api/v1.4/payments/charge-card-token/', false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Authorization: Token <your_api_key>\r\nContent-Type: application/json",
'content' => json_encode([
'amount' => '150',
'amount_currency' => 'AED',
'operation' => 'purchase',
'webhook_url' => 'https://yourbackend.com/webhooks/moneyhash',
'card_token' => '<hashid>'
])
]
]));
$data = json_decode($response, true);card_token_id must be the hashid — available as data.card_token.hashid in the card_token.created webhook, or as saved_cards[].id in the intent response. CVV is required if requires_cvv: true was returned in the card_token.created webhook.
Closing an intent
An intent can be closed in two ways:
- Manually - call the close endpoint directly to prevent further payment attempts, for example when a customer abandons checkout. API Reference
- Automatically via a flow - pass a
flow_idat intent creation to run a MoneyHash payment flow. The flow can be configured to close the intent automatically after a specific scenario completes.
curl --request POST \
--url https://web.moneyhash.io/api/v1.4/payments/intents/{intent_id}/close/ \
--header 'Authorization: Token <your_api_key>'await fetch(`https://web.moneyhash.io/api/v1.4/payments/intents/${intentId}/close/`, {
method: 'POST',
headers: { 'Authorization': 'Token <your_api_key>' }
});requests.post(
f'https://web.moneyhash.io/api/v1.4/payments/intents/{intent_id}/close/',
headers={'Authorization': 'Token <your_api_key>'}
)file_get_contents(
"https://web.moneyhash.io/api/v1.4/payments/intents/{$intentId}/close/",
false,
stream_context_create(['http' => [
'method' => 'POST',
'header' => "Authorization: Token <your_api_key>"
]])
);API reference
| Endpoint | Method | Purpose |
|---|---|---|
/api/v1.4/payments/intent/ | POST | Create payment intent |
/api/v1.4/payments/intents/{intent_id}/ | GET | Get intent details |
/api/v1.4/payments/charge-card-token/ | POST | Charge card token |
/api/v1.4/payments/intents/{intent_id}/transactions/ | GET | Get intent transactions |
/api/v1.4/payments/intents/{intent_id}/close/ | POST | Close intent |
Next steps
| I want to... | Go here |
|---|---|
| Add the Embedded checkout to my frontend | Embedded Experience |
| Build a custom checkout UI | SDK integration guide |
| Understand Intent, Transaction, and Operation statuses | Payment Components |
| Set up webhook signature verification | Webhook Signature |
| Configure redirect URLs | Redirects |
Updated about 1 month ago