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.

Before you start

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:

Integration typeEndpoint
Payment APIsPOST /api/v1.4/payments/intent/ - Reference
External APIPOST /api/v1.1/external/payments/intent/ - Reference
Direct APIPOST /api/v1.1/direct/payments/intent/ - Reference

Required parameters

ParameterTypeDescription
amountstringThe amount to charge. Max 14 digits, max 2 decimal places.
amount_currencystringISO 4217 currency code e.g. AED, USD, SAR.
webhook_urlstringYour backend endpoint that receives payment event notifications.
operationstringpurchase 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
}
Idempotency

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.

Redirect URLs

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:

FieldExpected valuePurpose
typetransaction.purchase.successfulConfirms the event type
intent.payment_status.statusCAPTUREDConfirms money was collected
intent.idYour order's intent IDLinks 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:

FieldExpected valuePurpose
typeintent.processedConfirms the event type
data.intent.statusPROCESSEDConfirms intent is terminal
data.intent.payment_status.statusCAPTUREDConfirms 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-sideSDKs
Understand integration types in depthIntegration Types Overview
Understand Intent, Transaction, and Operation statusesPayment Components
Set up webhook signature verificationWebhook Signature
Know about external flows discrepanciesExpiring and Closing Intents

Did this page help you?