Create a Payment
This guide walks through the end-to-end flow of a basic payment - from creating an intent on your backend to confirming the outcome via webhooks. It uses the Embedded Experience as the client-side layer, which is the fastest path to a working checkout.
This guide assumes your account is configured in the MoneyHash dashboard and you have connected at least one payment provider. You can test the full flow using the interactive sandbox
Step 1 - Create an intent
An intent represents a payment session mapped to your order. Create it from your backend on checkout initiation - never from the client side.
The endpoint you call depends on your integration type:
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. |
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"
}'Example response
The response includes embed_url - the URL you use in the next step to render the checkout.
{
"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
}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. You can embed it as an iframe or redirect the customer to it directly.
Iframe
<iframe
src="https://embed.moneyhash.io/embed/payment/LkGoG41"
width="100%"
height="600px"
frameborder="0">
</iframe>Redirect
<a href="https://embed.moneyhash.io/embed/payment/LkGoG41">Pay now</a>The customer selects a payment method and completes the payment inside the embedded experience. MoneyHash handles method display, card entry, 3DS, and provider communication.
For redirect URLs - to define where the customer lands after a successful, failed, or expired payment - pass successful_redirect_url, failed_redirect_url, and processed_redirect_url at intent creation. See Redirects for details.
Step 3 - Handle the response
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
What to check:
| 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": "LkGoG41",
"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
What to check:
| 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": "LkGoG41",
"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, retry behavior, and advanced scenarios - see Webhooks.
Next steps
| I want to... | Go here |
|---|---|
| Use the SDK instead of Embedded for the client-side | SDKs |
| Understand integration types in depth | Integration Types Overview |
| Understand Intent, Transaction, and Operation statuses | Payment Components |
| Set up webhook signature verification | Webhook Signature |
| Know about external flows discrepancies | Expiring and Closing Intents |
Updated 29 days ago