Skip to content

How it works

Two flows, opposite directions. Read both before writing any code: most integration mistakes come from assuming the money-in flow is synchronous, or that a payout is finished when the API answers.

Your customer owes you money. You ask Moria for something they can pay with — a virtual account number, a QR code, or an e-wallet checkout link — and Moria tells you when it is paid.

sequenceDiagram
    autonumber
    participant C as Customer
    participant Y as You
    participant M as Moria
    participant P as Provider

    Y->>M: POST /payments
    M->>P: create instrument
    P-->>M: VA / QR / link
    M-->>Y: 200 · pending
    Y-->>C: show payment code

    Note over C,M: minutes or days

    C->>P: pays
    P-->>M: settlement
    M->>M: credit balance
    M-->>Y: payment.succeeded

The API answers immediately, and it answers pending. That is the instrument being ready, not the money having arrived. The money arrives later — sometimes days later — and you learn about it from the webhook.

If the customer never pays, the instrument expires and you get payment.expired. If the provider rejects the payment, you get payment.failed. Every payment ends in exactly one terminal state.

Your balance with Moria is money you have collected, less fees. A payout moves some of it to a bank account.

sequenceDiagram
    autonumber
    participant Y as You
    participant M as Moria
    participant P as Provider
    participant B as Bank

    Y->>M: POST /payouts
    M->>M: check limits, reserve
    M-->>Y: 200 · pending
    M->>P: transfer
    P->>B: transfer
    P-->>M: settled / failed
    M-->>Y: payout.succeeded / failed

The reservation happens before the transfer is sent, so the same money cannot be promised twice. If the payout fails, the reservation is returned and your balance goes back up.

flowchart TD
    A["Customer pays<br/>(gross)"] --> B["− Combined fee"]
    B --> C["= Your balance<br/>(net)"]
    C --> D["Payout to your bank"]

The Partner Gateway defaults to DEDUCTED: amount is what the payer pays, and the combined fee comes out of it. A negotiated partner- or organisation-scoped rule may use ON_TOP, where the payer covers the fee above the amount that should land. The mode is resolved by Moria’s pricing configuration, not sent as a request field.

Every payment exposes three partner-facing figures:

FieldWhat it is
amountGross — what the customer paid
feeThe combined provider fee and Moria fee
net_amountWhat lands in your balance

The provider/margin split remains inside Moria for accounting and reconciliation; it is not exposed on the Partner Gateway surface. For a supported money-in instrument, the provider-facing gross must also be a whole IDR amount. See the changelog for the exact validation and migration notes.

StatusMeaningFinal?
pendingWaiting. For a payment: nobody has paid. For a payout: the outcome is not yet known.No
succeededThe money moved.Yes
failedIt did not, and will not under this request.Yes
expiredThe instrument was never paid in time.Yes
cancelledCalled off before it completed.Yes
refundedPaid, then returned.Yes

Every response carries is_final so you do not have to keep your own list of which states are terminal. When we add a state, is_final keeps telling the truth and your code does not change.

Poll, do not re-send. If a call times out or the connection drops, you do not know whether it worked. Ask for the status — never issue the same instruction again with a fresh idempotency key. See Idempotency.

Deduplicate on the webhook’s id. Delivery is at-least-once by design. The same event will sometimes arrive twice, and treating the second one as a second payment is a real way to double-credit a customer. See Webhooks.