Malum API Reference
Accept card payments risk-free, receive crypto instantly, and build powerful merchant flows. This reference covers every live endpoint — authentication, transactions, payouts, payment links, products, storefronts and more.
https://malum.co
Format: JSON
Auth: MALUM header
Getting started #
The Malum API is a JSON-over-HTTPS interface. Every endpoint lives
under https://malum.co and returns JSON. Authentication
is handled through a single header — no OAuth, no signatures to
calculate on every call.
Base URL
https://malum.coA quick taste
Create a $9.99 USD payment and receive a hosted checkout URL.
curl -X POST https://malum.co/api/v2/payment/create \
-H "MALUM: PXKGI625AG2:sec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"amount": 9.99,
"currency": "USD",
"customer_email": "[email protected]",
"webhook_url": "https://yoursite.com/webhook",
"success_url": "https://yoursite.com/thanks",
"cancel_url": "https://yoursite.com/cancelled"
}'const res = await fetch("https://malum.co/api/v2/payment/create", {
method: "POST",
headers: {
"MALUM": "PXKGI625AG2:sec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 9.99,
currency: "USD",
customer_email: "[email protected]",
webhook_url: "https://yoursite.com/webhook",
success_url: "https://yoursite.com/thanks",
cancel_url: "https://yoursite.com/cancelled",
}),
});
const data = await res.json();
console.log(data.link); // https://malum.co/checkout/TRN_XXXXXX$ch = curl_init("https://malum.co/api/v2/payment/create");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"MALUM: PXKGI625AG2:sec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"amount" => 9.99,
"currency" => "USD",
"customer_email" => "[email protected]",
"webhook_url" => "https://yoursite.com/webhook",
"success_url" => "https://yoursite.com/thanks",
"cancel_url" => "https://yoursite.com/cancelled",
]),
]);
$res = json_decode(curl_exec($ch), true);
echo $res["link"];{
"status": "success",
"transaction_id": "TRN_6F0B93A9A2C6",
"link": "https://malum.co/checkout/TRN_6F0B93A9A2C6",
"bpf": false,
"mgf": false,
"timestamp": 1713705932
}Authentication #
Authenticate every request by sending a MALUM header
containing your Merchant ID and private key, separated
by a colon. You can generate and rotate keys in the
API section of your dashboard.
MALUM: {mid}:{private_key}| Field | Format | Description |
|---|---|---|
| mid | string | Your Merchant ID (e.g. PXKGI625AG2). |
| private_key | string | Live secret beginning with sec_, or sandbox secret starting with sbx_. |
Missing or invalid auth
200 / 401{
"status": "error",
"message": "Invalid API Key."
}Sandbox mode #
Every live key comes with a paired sandbox key prefixed with
sbx_. Authenticate with the sandbox key and the entire
stack switches into simulation: no real charges are taken, no crypto
moves, and no webhooks are sent to third parties.
- All write endpoints behave identically to production.
- Transactions are flagged
is_sandbox = 1. - Payouts are disabled in sandbox — use your live key for real withdrawals.
- Use the sandbox controls endpoint to
force a transaction to
COMPLETEDorCANCELLED.
Account #
Manage your merchant balance, supported currencies, and crypto payouts.
Retrieve balance
Returns your available and pending balance (in USD).
curl https://malum.co/api/v3/account/balance \
-H "MALUM: $MALUM_KEY"const r = await fetch("https://malum.co/api/v3/account/balance", {
headers: { "MALUM": process.env.MALUM_KEY },
});
const { data } = await r.json();
console.log(data.balance); // "1,204.55"{
"status": "success",
"message": "Balanced retrieved.",
"data": {
"balance": "1,204.55",
"pending": "89.12",
"currency": "USD"
}
}List withdrawable currencies
Returns every crypto ticker + network you can currently withdraw to, along with minimum amounts and network fees.
curl https://malum.co/api/v3/account/currencies \
-H "MALUM: $MALUM_KEY"{
"status": "success",
"message": "Currencies retrieved.",
"data": [
{ "short": "USDT", "network": "TRON", "min_withdraw": 5.00, "tx_fee": 1.00 },
{ "short": "USDC", "network": "BASE", "min_withdraw": 2.00, "tx_fee": 0.10 },
{ "short": "BTC", "network": "BITCOIN", "min_withdraw": 10.0, "tx_fee": 2.50 }
]
}Request a crypto payout
Debits your balance and dispatches a crypto transfer to the supplied wallet. 1 % platform fee applies, plus an instant-fee multiplier defined on your account.
| Field | Type | Description |
|---|---|---|
| amountrequired | number | USD amount to withdraw. 1 < amount < 100 000. |
| currencyrequired | string | Crypto ticker, e.g. USDT, USDC, BTC. |
| networkrequired | string | Network identifier, e.g. TRON, BASE, BITCOIN. |
| addressrequired | string | Destination wallet address. Max 255 chars, no spaces. |
curl -X POST https://malum.co/api/v3/account/payout \
-H "MALUM: $MALUM_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 125.00,
"currency": "USDT",
"network": "TRON",
"address": "TXYZ123456789abcdefgHIJKLmnopQR"
}'await fetch("https://malum.co/api/v3/account/payout", {
method: "POST",
headers: {
"MALUM": process.env.MALUM_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 125,
currency: "USDT",
network: "TRON",
address: "TXYZ123456789abcdefgHIJKLmnopQR",
}),
});{
"status": "success",
"message": "Withdrawal of 125.00 USD to TXYZ123… initiated.",
"data": {
"order_id": "PO_663f8e5c2a8d1",
"link": "https://malum.co/payout/5f2f3a04-..."
}
}{ "status": "error", "message": "Insufficient funds." }
{ "status": "error", "message": "Daily withdrawal limit reached (3000/3000)." }
{ "status": "error", "message": "Currency or Network not supported." }
{ "status": "error", "message": "Travel Rule match detected. Payout is blocked pending verification." }
{ "status": "error", "message": "Payouts are not available in sandbox mode. Use your live API key." }Payments #
Create hosted checkouts, recurring subscriptions, and look up their status.
Create a payment
Creates a one-off transaction and returns a hosted checkout URL where the customer completes payment via card, crypto or bank transfer.
| Field | Type | Description |
|---|---|---|
| amountrequired | number | Amount in currency. Min $0.50 USD equivalent, max $10,500 USD. |
| currencyrequired | string | 3-character ISO code: USD, EUR, GBP, … |
| customer_emailrequired | string | Customer's email. Must be a deliverable address. |
| webhook_urlrequired | string | Your server endpoint for payment events. Max 255 chars. |
| success_urlrequired | string | Redirect after successful payment. |
| cancel_urlrequired | string | Redirect if the customer cancels. |
| buyer_pays_feesoptional | bool | Add processor fees on top of the price, charged to the buyer. |
| merchant_pays_gw_feesoptional | bool | You absorb all gateway fees (min $10). Mutually exclusive with buyer_pays_fees. |
| metadataoptional | string | Free-text up to 254 chars, echoed back in webhooks. |
| product_titleoptional | string | Displayed on the hosted checkout page. |
| product_descriptionoptional | string | Short description shown next to the title. |
| product_imageoptional | string | Public URL for a thumbnail. |
| product_linkoptional | string | Deep link (e.g. to your own product page). |
| product_markdownoptional | string | Markdown body rendered on the checkout page. |
curl -X POST https://malum.co/api/v2/payment/create \
-H "MALUM: $MALUM_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 49.99,
"currency": "USD",
"customer_email": "[email protected]",
"webhook_url": "https://yoursite.com/webhooks/malum",
"success_url": "https://yoursite.com/order/success",
"cancel_url": "https://yoursite.com/order/cancelled",
"buyer_pays_fees": true,
"metadata": "order=1042",
"product_title": "Pro subscription",
"product_image": "https://yoursite.com/img/pro.png"
}'const r = await fetch("https://malum.co/api/v2/payment/create", {
method: "POST",
headers: {
"MALUM": process.env.MALUM_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 49.99,
currency: "USD",
customer_email: "[email protected]",
webhook_url: "https://yoursite.com/webhooks/malum",
success_url: "https://yoursite.com/order/success",
cancel_url: "https://yoursite.com/order/cancelled",
buyer_pays_fees: true,
metadata: "order=1042",
}),
});
const { link, transaction_id } = await r.json();
// Redirect the customer:
window.location = link;$ch = curl_init("https://malum.co/api/v2/payment/create");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"MALUM: " . getenv("MALUM_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"amount" => 49.99,
"currency" => "USD",
"customer_email" => "[email protected]",
"webhook_url" => "https://yoursite.com/webhooks/malum",
"success_url" => "https://yoursite.com/order/success",
"cancel_url" => "https://yoursite.com/order/cancelled",
"buyer_pays_fees" => true,
]),
]);
$res = json_decode(curl_exec($ch));
header("Location: " . $res->link);{
"status": "success",
"transaction_id": "TRN_6F0B93A9A2C6",
"link": "https://malum.co/checkout/TRN_6F0B93A9A2C6",
"bpf": true,
"mgf": false,
"timestamp": 1713705932
}Create a subscription
Creates a recurring subscription that re-bills the customer every billing_period_in_days days and sends them a payment reminder payment_reminder days before renewal.
| Field | Type | Description |
|---|---|---|
| customer_emailrequired | string | Subscriber's email address. |
| amountrequired | number | Recurring amount. Min 4.99, max 4999.99. |
| currencyrequired | string | 3-character ISO code. |
| billing_period_in_daysrequired | int | 1 – 365. How often the customer is billed. |
| payment_reminderrequired | int | Days before renewal a reminder email is sent. Must be ≤ billing period. |
| webhook_urlrequired | string | Your server endpoint for subscription events. |
| success_urlrequired | string | Post-payment redirect. |
| cancel_urlrequired | string | Cancel redirect. |
| buyer_pays_feesrequired | 0 | 1 | Add fees on top for the buyer. |
| metadatarequired | string | Free-text up to 254 chars (can be empty). |
curl -X POST https://malum.co/api/v2/payment/create_subscription \
-H "MALUM: $MALUM_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_email": "[email protected]",
"amount": 19.99,
"currency": "USD",
"billing_period_in_days": 30,
"payment_reminder": 3,
"webhook_url": "https://yoursite.com/webhooks/malum",
"success_url": "https://yoursite.com/subscribed",
"cancel_url": "https://yoursite.com/cancelled",
"buyer_pays_fees": 0,
"metadata": "plan=pro"
}'{
"status": "success",
"message": "Subscription created.",
"data": { "checkout_id": "TRN_8F2AB94A7123" }
}Look up a transaction
Returns the public state of a transaction, plus the payment methods
available on its hosted checkout. Useful for rendering your own order
status page.
Also available as /api/v2/payment/lookup?txid=....
| Field | Type | Description |
|---|---|---|
| txidrequired | path | The transaction ID, e.g. TRN_6F0B93A9A2C6. |
curl https://malum.co/api/lookup/TRN_6F0B93A9A2C6{
"status": "success",
"data": {
"tid": "TRN_6F0B93A9A2C6",
"amount": "49.99",
"currency": "USD",
"amount_in_usd": "49.99",
"status": "PROCESSING",
"business_info": {
"business_name": "Acme Inc.",
"business_logo_uri": "https://cdn.malum.co/logos/acme.png"
},
"payment_methods": {
"card": { "alt": "Visa / Mastercard", "url": "https://malum.co/checkout/TRN_.../card" },
"usdt": { "alt": "Tether (TRC20)", "url": "https://malum.co/checkout/TRN_.../usdt" }
}
}
}Create a payment from an HTML form
Lets you wire up a plain <form> that posts directly to
Malum and lands the customer on the hosted checkout — no JavaScript
required. Sign each form server-side with your private key.
Build the signature as:
$sig = md5(
$amount . $currency . $webhook_url . $success_url . $cancel_url .
$customer_email . $buyer_pays_fees . $metadata . $business_id .
$private_key
);If you set any product_* field, also compute signed_product_hash:
$sph = md5(
$product_markdown . $product_title . $product_image .
$product_description . $product_link . $private_key
);<form method="POST" action="https://malum.co/api/v3/checkout/form">
<input type="hidden" name="amount" value="49.99">
<input type="hidden" name="currency" value="USD">
<input type="hidden" name="customer_email" value="[email protected]">
<input type="hidden" name="webhook_url" value="https://yoursite.com/wh">
<input type="hidden" name="success_url" value="https://yoursite.com/ok">
<input type="hidden" name="cancel_url" value="https://yoursite.com/no">
<input type="hidden" name="business_id" value="PXKGI625AG2">
<input type="hidden" name="signed_message" value="{{ $sig }}">
<button type="submit">Pay</button>
</form>https://malum.co/checkout/{transaction_id}. The customer
never sees the raw endpoint.
Complete or cancel a sandbox transaction
Only works on transactions flagged is_sandbox = 1. Use it
to simulate a successful or cancelled payment without going through
a payment processor.
| Field | Type | Description |
|---|---|---|
| payTokenrequired | query | Sandbox transaction ID. |
| actionrequired | query | complete or cancel. |
# Force-complete a sandbox transaction
curl "https://malum.co/api/v3/checkout/sandbox?payToken=TRN_SANDBOX&action=complete"On success you are redirected to the transaction's success_url or cancel_url.
Payment Links #
Reusable hosted payment pages. Create a link once, share it anywhere, and optionally cap the number of uses. Every charge triggers the same webhook as a regular transaction.
Create payment link #
/api/v3/payment-links/create
Body
| Field | Type | Description |
|---|---|---|
title required | string | Link title, max 255 chars. |
amount required | float | Amount in currency. Must be $0.50–$10,500 USD equivalent. |
currency required | string | 3-char ISO code (USD, EUR, GBP…). |
description | string | Short description shown on the checkout page. |
webhook_url | URL | POST endpoint for transaction events. |
success_url | URL | Redirect after successful checkout. |
cancel_url | URL | Redirect after checkout is cancelled. |
buyer_pays_fees | bool | Surface gateway fees onto the buyer. |
merchant_pays_gw_fees | bool | Merchant absorbs gateway fees (mutually exclusive with buyer_pays_fees). |
product_image | URL | Product image displayed on the page. |
product_link | URL | External link to the product being sold. |
product_markdown | string | Rich markdown rendered above the pay button. |
max_uses | int | 0 for unlimited, otherwise the cap. Default 0. |
curl -X POST https://malum.co/api/v3/payment-links/create \
-H "MALUM: 12345:sk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"title": "Pro plan — monthly",
"amount": 29.99,
"currency": "USD",
"description": "Unlimited seats + priority support",
"webhook_url": "https://yourapp.com/webhooks/malum",
"max_uses": 0
}'const res = await fetch("https://malum.co/api/v3/payment-links/create", {
method: "POST",
headers: {
"MALUM": "12345:sk_live_xxxxxxxxxxxx",
"Content-Type": "application/json"
},
body: JSON.stringify({
title: "Pro plan — monthly",
amount: 29.99,
currency: "USD",
max_uses: 0
})
});
const data = await res.json();
console.log(data.link);$ch = curl_init("https://malum.co/api/v3/payment-links/create");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"MALUM: 12345:sk_live_xxxxxxxxxxxx",
"Content-Type: application/json"
],
CURLOPT_POSTFIELDS => json_encode([
"title" => "Pro plan — monthly",
"amount" => 29.99,
"currency" => "USD",
]),
]);
$res = json_decode(curl_exec($ch), true);
echo $res['link'];Response 200 OK
{
"status": "success",
"plid": "PL_2h91SmA4k",
"slug": "pro-plan-monthly-2h91",
"link": "https://malum.co/link/pro-plan-monthly-2h91",
"timestamp": 1713699200
}List payment links #
/api/v3/payment-links/list
Returns every payment link belonging to the authenticated merchant.
curl https://malum.co/api/v3/payment-links/list \
-H "MALUM: 12345:sk_live_xxxxxxxxxxxx"Response 200 OK
{
"status": "success",
"count": 1,
"links": [
{
"plid": "PL_2h91SmA4k",
"slug": "pro-plan-monthly-2h91",
"link": "https://malum.co/link/pro-plan-monthly-2h91",
"title": "Pro plan — monthly",
"description": "Unlimited seats + priority support",
"amount": 29.99,
"currency": "USD",
"buyer_pays_fees": false,
"merchant_pays_gw_fees": false,
"max_uses": 0,
"use_count": 14,
"is_active": true,
"created_at": "2026-04-01 18:22:01"
}
]
}Toggle payment link #
/api/v3/payment-links/toggle
Flip the link between active and inactive. Disabled links return a 410 to buyers.
Body
| Field | Type | Description |
|---|---|---|
plid required | string | Link identifier returned from create. |
curl -X POST https://malum.co/api/v3/payment-links/toggle \
-H "MALUM: 12345:sk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"plid":"PL_2h91SmA4k"}'Delete payment link #
/api/v3/payment-links/delete
Permanently remove a link. Existing transactions created from the link are retained.
Body
| Field | Type | Description |
|---|---|---|
plid required | string | Link identifier. |
curl -X DELETE https://malum.co/api/v3/payment-links/delete \
-H "MALUM: 12345:sk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"plid":"PL_2h91SmA4k"}'Products #
Sell one-off digital goods with automatic fulfilment. Products support
three delivery types: text_message (custom message),
link (redirect URL), and serial_key
(consume from a stock of pre-uploaded keys).
Create product #
/api/v3/products/create
Body
| Field | Type | Description |
|---|---|---|
title required | string | Max 255 chars. |
price required | float | $0.50–$10,500 USD equivalent. |
currency required | string | 3-char ISO code. |
product_type required | enum | text_message, link, or serial_key. |
delivery_content required | string | Message body, redirect URL, or first serial key. |
description | string | Optional long description. |
instructions | string | Post-purchase instructions. |
image_path | string | Path returned by /upload-image. |
webhook_url, success_url, cancel_url | URL | Optional lifecycle URLs. |
buyer_pays_fees, merchant_pays_gw_fees | bool | Fee handling, mutually exclusive. |
show_on_storefront | bool | List on your public storefront. Default true. |
curl -X POST https://malum.co/api/v3/products/create \
-H "MALUM: 12345:sk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"title": "Ebook: Tactical Crypto",
"price": 14.99,
"currency": "USD",
"product_type": "link",
"delivery_content": "https://files.example.com/ebook.pdf",
"instructions": "Download within 24h, link expires afterwards.",
"show_on_storefront": true
}'const res = await fetch("https://malum.co/api/v3/products/create", {
method: "POST",
headers: {
"MALUM": "12345:sk_live_xxxxxxxxxxxx",
"Content-Type": "application/json"
},
body: JSON.stringify({
title: "Ebook: Tactical Crypto",
price: 14.99,
currency: "USD",
product_type: "link",
delivery_content: "https://files.example.com/ebook.pdf"
})
});
const data = await res.json();$ch = curl_init("https://malum.co/api/v3/products/create");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"MALUM: 12345:sk_live_xxxxxxxxxxxx",
"Content-Type: application/json"
],
CURLOPT_POSTFIELDS => json_encode([
"title" => "Ebook: Tactical Crypto",
"price" => 14.99,
"currency" => "USD",
"product_type" => "link",
"delivery_content" => "https://files.example.com/ebook.pdf",
]),
]);
$res = json_decode(curl_exec($ch), true);Response 200 OK
{
"status": "success",
"pid": "PROD_8A3k2xN1",
"slug": "ebook-tactical-crypto",
"link_slug": "ebook-tactical-crypto-8a3k",
"link": "https://malum.co/link/ebook-tactical-crypto-8a3k",
"timestamp": 1713699305
}Get product #
/api/v3/products/get?pid=PROD_XXX
Query parameters
| Field | Type | Description |
|---|---|---|
pid required | string | Product identifier. |
curl "https://malum.co/api/v3/products/get?pid=PROD_8A3k2xN1" \
-H "MALUM: 12345:sk_live_xxxxxxxxxxxx"serial_key products, the
response hides delivery_content and instead returns a
live stock_count.
Update product #
/api/v3/products/update
Same body schema as create, with pid required and everything else overwriting existing values.
Toggle product #
/api/v3/products/toggle
Enable or disable the product. Disabled products 404 on the storefront.
curl -X POST https://malum.co/api/v3/products/toggle \
-H "MALUM: 12345:sk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"pid":"PROD_8A3k2xN1"}'Delete product #
/api/v3/products/delete
Removes the product. Historical purchases remain in the transactions log.
curl -X DELETE https://malum.co/api/v3/products/delete \
-H "MALUM: 12345:sk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"pid":"PROD_8A3k2xN1"}'Upload product image #
/api/v3/products/upload-image
Multipart upload, field name image. Returns a path you can pass as image_path when creating or updating a product.
| Constraint | Value |
|---|---|
| Max size | 5 MB |
| Allowed types | JPG, PNG, GIF, WEBP |
curl -X POST https://malum.co/api/v3/products/upload-image \
-H "MALUM: 12345:sk_live_xxxxxxxxxxxx" \
-F "image=@/path/to/cover.png"Response 200 OK
{
"status": "success",
"url": "/assets/uploads/products/12345/8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d.png",
"timestamp": 1713699512
}Storefront #
Your public storefront at malum.co/store/{slug}. These
endpoints control branding, media galleries, and which alternative
payment methods are accepted directly from your own processor credentials.
Save storefront settings #
/api/v3/storefront/save
Partial update — only fields present in the body are written.
Body
| Field | Type | Description |
|---|---|---|
shop_slug | string | Unique URL slug (a–z, 0–9, _, -). |
shop_name | string | Display name, max 100 chars. |
description | string | Max 2000 chars. |
meta_title, meta_description | string | SEO metadata. |
meta_image_path, logo_path, favicon_path, background_path | string | Paths returned by /upload. |
ga_id, crisp_id, tawkto_id | string | Analytics / chat IDs. |
social_discord, social_youtube, social_telegram, social_tiktok, social_instagram | URL | Social links shown in the footer. |
curl -X POST https://malum.co/api/v3/storefront/save \
-H "MALUM: 12345:sk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"shop_slug": "mystore",
"shop_name": "My Store",
"description": "Premium digital goods, shipped instantly."
}'Upload storefront asset #
/api/v3/storefront/upload
Multipart upload, field name image. Returns a path suitable for logo/favicon/background fields.
| Constraint | Value |
|---|---|
| Max size | 5 MB |
| Allowed types | JPG, PNG, GIF, WEBP, ICO |
curl -X POST https://malum.co/api/v3/storefront/upload \
-H "MALUM: 12345:sk_live_xxxxxxxxxxxx" \
-F "image=@/path/to/logo.png"Media pictures #
/api/v3/storefront/media-pictures
Replace the storefront gallery. Maximum 30 pictures.
Body
| Field | Type | Description |
|---|---|---|
enabled | bool | Show the gallery on the storefront. |
pictures | string[] | Array of upload paths, in display order. |
{
"enabled": true,
"pictures": [
"/assets/uploads/storefront/12345/a1b2c3.png",
"/assets/uploads/storefront/12345/d4e5f6.png"
]
}Media videos #
/api/v3/storefront/media-videos
Embed YouTube / Vimeo videos on your storefront. Maximum 20 URLs.
Body
| Field | Type | Description |
|---|---|---|
enabled | bool | Show the video section. |
videos | string[] | Array of video URLs. |
Payment methods #
/api/v3/storefront/payment-methods
/api/v3/storefront/payment-methods
Configure third-party gateways that run under your own merchant
accounts. Supported: stripe, mollie,
skrill, cryptomus, coinpayments.
GET. Values returned for
secret_key, webhook_secret, ipn_secret,
api_key and secret_word show only the last
four characters. If you POST a masked value back, the
server keeps the existing stored secret rather than overwriting it.
POST body
{
"methods": {
"stripe": {
"is_enabled": true,
"credentials": {
"publishable_key": "pk_live_xxx",
"secret_key": "sk_live_xxx",
"webhook_secret": "whsec_xxx"
}
},
"coinpayments": {
"is_enabled": true,
"credentials": {
"merchant_id": "xxxxxx",
"ipn_secret": "xxxxxx"
}
}
}
}Utility #
Public helpers for exchange rates, fee calculations and metadata. Most are keyless.
Exchange rates #
/api/v2/exchange/{from}-{to}
/api/rates/{from}-{to}
Rates are refreshed hourly and calculated against USD. Both pretty-URL forms and the query-string form are supported.
Parameters
| Field | Type | Description |
|---|---|---|
from | string | 3-char ISO source currency. |
to | string | 3-char ISO target currency. |
curl https://malum.co/api/v2/exchange/USD-EUR
# or
curl https://malum.co/api/rates/USDEURResponse 200 OK
{
"status": "success",
"from": "USD",
"to": "EUR",
"rate": 0.9124,
"timestamp": 1713699721
}Calculate fees #
/api/v2/service/calcfees
Preview the exact charge the buyer will see for a given transaction and payment method — useful before redirecting to checkout.
Query parameters
| Field | Type | Description |
|---|---|---|
payToken required | string | The transaction ID. |
payMethod required | string | Payment method slug (see Coins). |
curl "https://malum.co/api/v2/service/calcfees?payToken=TRN_xxx&payMethod=bitcoin"Coins / payment methods #
/api/v2/service/coins
Returns the complete list of supported crypto coins and alternative payment methods, including their slugs, icons, and network names.
curl https://malum.co/api/v2/service/coinsService fees #
/api/v2/service/servicefee
Returns per-payment-method processor fees (fixed + percentage + minimum).
IP lookup #
/api/v2/ip
GeoIP lookup with city, country and ASN — used by the Malum fraud engine, exposed for convenience.
Query parameters
| Field | Type | Description |
|---|---|---|
ip | string | IPv4 or IPv6. Defaults to the caller IP. |
curl "https://malum.co/api/v2/ip?ip=8.8.8.8"Discord bot link #
/api/v2/discord/link
Authenticates a Discord server against the Malum merchant account it was paired with during the bot install flow.
Body
| Field | Type | Description |
|---|---|---|
GUID required | string | Discord server (guild) ID. |
Key required | string | One-time pairing key issued in the merchant dashboard. |
Response 200 OK
{
"status": "success",
"message": "linked successfully",
"apiKey": "a1b2c3d4e5f6...",
"timestamp": 1713699821
}Reseller #
The Reseller API powers crypto-for-fiat rails built on top of Malum's
liquidity. It does not require a MALUM API key — pricing is computed
against USD and each order is scoped by a caller-supplied
external_reference. All endpoints accept
GET parameters.
Payment options #
/api/v3/reseller/payment_options
Price-check available crypto rails against a fiat amount.
Query parameters
| Field | Type | Description |
|---|---|---|
fiat_amount required | float | Value between 0 and 10,000. |
fiat_currency required | string | 3-char ISO code. |
curl "https://malum.co/api/v3/reseller/payment_options?fiat_amount=150&fiat_currency=USD"Create order #
/api/v3/reseller/checkout
Creates a pending reseller order. Returns a transaction_code (UUID4) used when requesting the payment link.
Query parameters
| Field | Type | Description |
|---|---|---|
fiat_amount required | float | 0<x≤10,000. |
fiat_currency required | string | 2–4 letter ISO code. |
crypto_currency required | string | e.g. BTC, USDT. |
crypto_currency_network required | string | e.g. BTC, TRX, ETH. |
wallet_address required | string | Destination wallet for the customer. |
external_reference required | string | Your internal order ID. |
webhook_url | URL | Optional event receiver. |
| Partner split — all fields below must be supplied together or omitted entirely. | ||
partner_wallet_address | string | Partner payout wallet. |
partner_crypto_currency | string | e.g. USDT. |
partner_crypto_currency_network | string | e.g. TRX. |
partner_comission_percentage | float | Commission to split off. |
partner_external_reference | string | Partner's internal ID. |
partner_webhook_url | URL | Partner webhook receiver. |
curl "https://malum.co/api/v3/reseller/checkout\
?fiat_amount=200\
&fiat_currency=USD\
&crypto_currency=USDT\
&crypto_currency_network=TRX\
&wallet_address=TXYZ...\
&external_reference=ORDER-9921\
&webhook_url=https://yourapp.com/hooks/reseller"Response 200 OK
{
"status": "success",
"message": "Order created successfully.",
"data": {
"transaction_code": "a1b2c3d4-e5f6-4789-9abc-deadbeefcafe",
"fiat_amount": 200,
"fiat_currency": "USD",
"crypto_currency": "USDT",
"crypto_currency_network": "TRX",
"wallet_address": "TXYZ...",
"status": "pending",
"payment_options": [ ... ]
}
}Request payment link #
/api/v3/reseller/payment_link
Given a pending transaction_code and a chosen rail, returns the hosted checkout URL.
Query parameters
| Field | Type | Description |
|---|---|---|
transaction_code required | UUID4 | Returned by /checkout. |
payment_method required | string | Slug from /payment_options. |
curl "https://malum.co/api/v3/reseller/payment_link\
?transaction_code=a1b2c3d4-e5f6-4789-9abc-deadbeefcafe\
&payment_method=bitcoin"Integrations #
Drop Malum into an existing checkout flow without building your own UI. Pick white-label for full JSON control or Shopify for a native payment app.
White-label checkout #
/api/v3/whitelabel/checkout/{payToken}
Returns everything needed to render your own checkout UI: business
branding, currency amount, customer email and the list of payment
methods available for the buyer's country (Cloudflare
CF-IPCountry header, or explicit country
override).
Query parameters
| Field | Type | Description |
|---|---|---|
country | string | ISO-2 override. Defaults to the caller's detected country. |
curl "https://malum.co/api/v3/whitelabel/checkout/TRN_xxx?country=US"Response 200 OK
{
"status": "success",
"message": "Checkout data fetched.",
"data": {
"business_logo_uri": "https://.../logo.png",
"business_name": "Acme Inc.",
"currency_amount": "49.99",
"currency_symbol": "USD",
"customer_email": "[email protected]",
"payment_methods": [ ... ]
}
}https://malum.co/checkout/{payToken} in a new tab.
Shopify #
Malum ships a native Shopify Payment App. These endpoints are called server-to-server by Shopify — you don't invoke them from your own code, but they're documented here for transparency and for self-hosted / dev-store installs.
/api/v3/shopify/payment-session
Triggered when the buyer selects Malum at Shopify checkout. Creates a Malum transaction and returns a redirect URL.
Headers
| Header | Description |
|---|---|
X-Shopify-Shop-Domain | The merchant's *.myshopify.com domain. |
X-Shopify-Hmac-Sha256 | HMAC-SHA256 of the raw request body, base64-encoded, using the stored webhook_secret. |
Body JSON from Shopify
{
"id": "pss_1a2b3c",
"gid": "gid://shopify/PaymentSession/pss_1a2b3c",
"amount": "49.99",
"currency": "USD",
"merchantReference": "#1001",
"customer": { "email": "[email protected]" }
}/api/v3/shopify/refund-session
Shopify initiates a refund. Malum issues a reversal on the original transaction.
/api/v3/shopify/webhook
Shopify lifecycle webhooks (uninstall, app deauthorization, GDPR data requests).
Errors & responses #
Every endpoint returns JSON. Success responses include
"status": "success"; failures return an HTTP error code
and a JSON body with status, error (or
message) and a timestamp.
Error shape
{
"status": "failed",
"error": "currency is not supported.",
"timestamp": 1713699999
}Common HTTP codes
| Code | Meaning |
|---|---|
200 | Request succeeded. |
400 | Bad request — missing or invalid parameter. |
401 | Missing or invalid MALUM header. |
403 | API key is valid but lacks permission for this resource. |
404 | Resource not found (transaction, product, link…). |
405 | Method not allowed — use the documented verb. |
418 | Malformed or impossible IP / geo payload. |
419 | Session / CSRF token invalid or expired. |
429 | Rate limited — back off and retry. |
500 | Malum-side error. Safe to retry with exponential backoff. |
create endpoints are not
idempotent by default — store the returned txid and
never re-send the same create call in a retry loop. For reliable
retries, deduplicate on your own order ID before calling.
Webhooks #
Malum delivers webhook events as HTTP POST requests with
a JSON body to the webhook_url you set on the
transaction, payment link or product.
Payload
{
"status": "COMPLETED",
"txn": "TRN_xxxxxxxxx",
"requested_amount": 9.99,
"requested_currency":"USD",
"cid": "CST_xxxxxxxxx",
"checkout": 9.99,
"metadata": "{\"order_id\":\"A-123\"}",
"buyer_pays_fees": 0,
"sandbox": false,
"timestamp": 1713699999,
"signature": "5f4dcc3b5aa765d61d8327deb882cf99"
}Event types
| Status | When it fires |
|---|---|
CREATED | New transaction registered but not paid. |
PROCESSING | Buyer submitted payment, awaiting confirmations. |
COMPLETED | Payment cleared — funds credited to the merchant balance. |
EXPIRED | Buyer never paid before the window elapsed. |
CANCELLED | Merchant or buyer cancelled the checkout. |
Signature verification
Every payload is signed with the merchant's webhook_key
(or sandbox_webhook_key when sandbox=true).
Compute:
signature = md5( txn + "|" + timestamp + "|" + webhook_key )<?php
$payload = json_decode(file_get_contents('php://input'), true);
$expected = md5(
$payload['txn'] . '|' .
$payload['timestamp'] . '|' .
getenv('MALUM_WEBHOOK_KEY')
);
if (!hash_equals($expected, $payload['signature'])) {
http_response_code(401);
exit('invalid signature');
}
// …mark order paid…
http_response_code(200);import crypto from "crypto";
import express from "express";
const app = express();
app.use(express.json());
app.post("/webhooks/malum", (req, res) => {
const { txn, timestamp, signature } = req.body;
const expected = crypto
.createHash("md5")
.update(`${txn}|${timestamp}|${process.env.MALUM_WEBHOOK_KEY}`)
.digest("hex");
if (expected !== signature) return res.status(401).end();
// …mark order paid…
res.sendStatus(200);
});2xx response a
success. Non-2xx responses are retried with exponential backoff for
up to 24 hours. Respond quickly (<12 s) to avoid timeouts.