Moyasar is a Saudi payment gateway. This skill encodes correct, secure
integration of its REST API. Payment code is high-stakes: a mistake can
double-charge a customer, leak a secret key, or mark an unpaid order as paid.
Follow the rules here rather than guessing; when a detail isn't covered, read
the relevant references/ file before writing code.
How to use this skill
- Read this file fully — it carries the model, the security rules, and the end-to-end flows that apply to every integration.
- For endpoint-level detail (every field, every status, every variant), open the matching reference file from the table below. Don't reconstruct request or response shapes from memory — they have subtle required fields.
| If the task involves… | Read |
|---|---|
| Charging a card/wallet, refund, capture, void, auth, payment status, splits, AFT | references/payments.md |
| Hosted invoices / payment links, bulk invoices | references/invoices.md |
| Saving cards / tokenization, charging a saved card | references/tokens.md |
| Sending money out: payout accounts, payouts, bulk payouts, internal transfers | references/payouts.md |
| Reconciliation: settlements, settlement lines, aggregation transfers | references/settlements.md |
| Receiving event notifications, webhook setup, retries, verifying webhooks | references/webhooks.md |
| Error shapes, HTTP codes, what to retry | references/errors.md |
| Moyasar Form, callback page, mobile SDK, Apple Pay session, BIN/issuer lookup | references/frontend.md |
| Node.js / TypeScript / Next.js (Axios) — typed client, App Router route handlers, callback & webhook verification | references/nodejs-typescript.md + bundled assets/moyasar-client.ts |
| Verifying a payment server-side | scripts/verify_payment.py (Python — run it / port it); TypeScript equivalent is verifyPayment() in assets/moyasar-client.ts |
The core model
- Base URL:
https://api.moyasar.com/v1for everything except aggregation transfers, which usehttps://apimig.moyasar.com/v1(see settlements ref). - All requests over HTTPS. Plain HTTP is rejected. No exceptions.
- JSON request and response bodies (form-encoded also accepted on input).
- Amounts are integers in the smallest currency unit, never decimals:
1.00 SAR→100(halalas)1.00 KWD→1000(fils)1 JPY→1(no minor unit) Compute asround(major_amount * minor_factor). Passing10.50where1050is expected is a common, costly bug.
- IDs are UUIDs (except token IDs, which look like
token_…). - Pagination: list endpoints return 40 items/page plus a
metaobject (current_page,next_page,prev_page,total_pages,total_count). Pass?page=N. Newest first. Some lists (e.g. internal transactions) returntotal_count: null— treat them as infinite-scroll, page untilnext_pageis null. - Metadata: optional key/value object on payments, invoices, payouts,
tokens, internal transactions. Up to 30 keys, key ≤ 40 chars, value ≤ 500
chars. Searchable via list endpoints'
metadata[key]=valuefilter. Unset a key by sending it empty; unset all by sending empty metadata. Never store card numbers, bank accounts, or any sensitive data in metadata.
Authentication — read this carefully
Moyasar uses HTTP Basic Auth with the API key as the username and an
empty password. The empty password matters: Authorization: Basic base64("<key>:")
(note the trailing colon, nothing after it).
curl https://api.moyasar.com/v1/payments -u sk_test_xxxxxxxx:
There are two key types, and confusing them is a security incident:
| Key | Prefix | Where it may live | What it can do |
|---|---|---|---|
| Publishable | pk_test_ / pk_live_ |
Browser, mobile app, public HTML — safe to ship to clients | Only Create Payment / create token |
| Secret | sk_test_ / sk_live_ |
Backend only — never in client code, repos, logs | Every operation |
Hard rules:
- Never put an
sk_*key in frontend code, mobile apps, HTML, query strings, or version control. If a secret key is exposed, it must be regenerated in the Moyasar Dashboard immediately — treat exposure as a breach. Secret keys are shown only once on creation; only the ID stays visible afterward. - Cardholder data must never reach the merchant backend. Sending raw card
numbers to your server violates the Moyasar agreement and terminates service.
Card data goes from the client directly to Moyasar via the publishable key
(Moyasar Form, SDK, or a direct Create Payment call with
pk_*). - Use environment variables / a secrets manager for keys. Different keys per environment.
pk_test_/sk_test_ = test mode (sandbox; no real money, no banking
network, no effect on live data). pk_live_/sk_live_ = live mode. The
mode is determined entirely by the key used. Always build and verify against
test mode with the documented test cards before switching keys to live.
Idempotency — prevents double charges
Network failures on Create Payment are ambiguous: a 5xx/timeout/connection
error doesn't tell you whether the customer was charged. To make retries safe,
generate a UUID (v4) yourself and send it as given_id in the Create Payment
body. That value becomes the payment's id.
- Retry Create Payment (with the same
given_id) only on:5xxresponse, network error, or open/read/write timeout. A4xxis a client error — fix the request, don't blind-retry. A2xxis conclusive — act on it. - If the first attempt already created the payment, the retry returns that same payment. If it didn't, the retry creates it. Either way: no double charge.
- Reusing a
given_idfor a different payment returns400 "Payment is already created."—given_idis a one-time idempotency key per payment.
Generate given_id per payment attempt, persist it with your order before the
request, and reuse it across retries of that same attempt.
Security best practices (this is the point of the skill)
These are non-negotiable for any production Moyasar integration:
- Verify every payment server-side before fulfilling the order. The
browser redirect to your
callback_url(carryingid,status,message) is not trustworthy — it can be replayed or tampered with. On the callback, take the paymentid, fetch it from your backend with the secret key, and confirm all of:status == "paid"(orcaptured),amountequals the order's expected amount, andcurrencymatches. Only then mark paid. Usescripts/verify_payment.pyor port its logic. - A
201/200does not mean "paid". Moyasar returns201for a payment that was created butfailed(e.g. card declined) — the failure is instatusandsource.message, not the HTTP code. Always branch on thestatusfield, never on the HTTP status alone. - Never trust client-supplied amounts for verification. Compare the
fetched payment's
amount/currencyagainst the order total stored on your server, not against anything from the browser. - Use
given_ididempotency for every Create Payment so retries can't double-charge. - Keep secret keys server-side only; rotate immediately on exposure.
- Webhooks: return a
2xxfast (before heavy work), verify thesecret_tokenagainst your configured shared secret, and make handlers idempotent — the same event can be delivered more than once and retried up to 5 times. Never rely on webhooks alone; also verify on the callback. Seereferences/webhooks.md. - Persist Moyasar payment IDs (and
given_id) on your orders for reconciliation, refunds, and support. - Don't poll when a webhook or callback will do; if you must poll a pending payment, back off and cap attempts.
End-to-end flows
A. Hosted Moyasar Form (recommended for web card payments)
Frontend renders Moyasar Form with the publishable key → customer pays card
data directly to Moyasar (never your server) → Moyasar redirects the browser to
your callback_url?id=…&status=… → your backend fetches the payment by id
with the secret key and verifies status+amount+currency → mark order paid.
Details and snippet: references/frontend.md.
B. API-direct card payment with 3-D Secure
Create Payment with a card source (from client, using pk_*) and a
callback_url. Response status is usually initiated with a
source.transaction_url — redirect the payer there for the 3DS challenge. They
return to callback_url; then verify server-side as in flow A. creditcard
and token sources require callback_url. See references/payments.md.
C. Authorize then capture (delayed capture)
Create Payment with source.manual: true → status: authorized (funds held,
not taken). Later call Capture (full or partial). If you don't capture in time
the issuer auto-voids it — and the status stays authorized, it is not
updated by Moyasar, so reconcile by time, not status alone. Cancel a hold with
Void. See references/payments.md.
D. Payment link / no frontend
Create an Invoice → present its url to the customer (Moyasar-hosted checkout)
→ on payment Moyasar POSTs the invoice to the invoice callback_url
(notification only, not a redirect) and redirects the payer to success_url.
Still verify server-side. See references/invoices.md.
E. Refund
POST /payments/:id/refund with optional amount (omit = full; partial
allowed up to captured/paid amount). Refunds a paid/captured payment;
status → refunded. See references/payments.md.
Common pitfalls
- Sending a decimal amount instead of the integer minor unit.
- Treating HTTP
201/200as success without checkingstatus. - Trusting the callback's
statusquery param without a server-side fetch. - Putting
sk_*in frontend/mobile/repo, or sending card data to your server. - Forgetting
callback_urlforcreditcard/tokensources (required). - Non-empty Basic Auth password (must be empty — key as username only).
- Blind-retrying Create Payment without
given_id(double-charge risk). - Assuming a webhook fires exactly once, or doing heavy work before the
2xx. - Using
apimig.moyasar.comfor normal APIs (only aggregation transfers), orapi.moyasar.comfor transfers. - Invoice
amountminimum is100; bulk invoices/payouts have batch caps.
Keep examples in the user's stack and idioms. The references use curl plus
Python/Node where the official docs do; the wire contract is identical across
languages, so translate faithfully. For Node.js / TypeScript / Next.js, do
not paste the docs' raw Axios/follow-redirects snippets (hardcoded keys,
:id placeholders, no verification) — use the bundled, typed
assets/moyasar-client.ts and the patterns in references/nodejs-typescript.md
instead; they encode the auth, idempotency, and server-side verification rules
above.