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:

  1. Access your MoneyHash Organization and Account from the dashboard
  2. Connect your payment providers to the account
  3. Configure your Payment Defaults and Flow
  4. 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

POST /api/v1.4/payments/intent/

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.

Recommended parameters

ParameterPurpose
billing_dataCustomer billing details passed to the provider automatically
customerAssociate with an existing MoneyHash customer ID
card_tokenPay with a saved token directly
allow_tokenize_cardSet to true to allow saving the card for future use
payment_methodPre-select a payment method to show on checkout
payment_typeSet to recurring for non-present cardholder payments
merchant_initiatedSet to true for merchant-initiated transactions
threeds.enabledSet to true to enable 3DS authentication
expires_after_secondsNumber of seconds after which the intent expires
successful_redirect_urlRedirect after successful payment
failed_redirect_urlRedirect after failed payment
pending_external_action_redirect_urlRedirect when external action is pending
processed_redirect_urlRedirect after intent is processed
time_expired_redirect_urlRedirect when intent expires
closed_redirect_urlRedirect 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.

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. 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.

SDK integration guide

Note

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
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": "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
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": "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

POST /api/v1.4/payments/charge-card-token/

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);
Note

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_id at intent creation to run a MoneyHash payment flow. The flow can be configured to close the intent automatically after a specific scenario completes.
POST /api/v1.4/payments/intents/{intent_id}/close/
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

EndpointMethodPurpose
/api/v1.4/payments/intent/POSTCreate payment intent
/api/v1.4/payments/intents/{intent_id}/GETGet intent details
/api/v1.4/payments/charge-card-token/POSTCharge card token
/api/v1.4/payments/intents/{intent_id}/transactions/GETGet intent transactions
/api/v1.4/payments/intents/{intent_id}/close/POSTClose intent

Next steps

I want to...Go here
Add the Embedded checkout to my frontendEmbedded Experience
Build a custom checkout UISDK integration guide
Understand Intent, Transaction, and Operation statusesPayment Components
Set up webhook signature verificationWebhook Signature
Configure redirect URLsRedirects

Did this page help you?