Skip to main content

Invoices & Payment Links

The Morph Payments API exposes these endpoints for collecting a payment:

  • Create Payment Link — a shareable checkout URL for a fixed amount.
  • Create Invoice — a payment link plus payer details, a due date, and optional line items.
  • Get / update an existing Payment Link or Invoice. What you can change depends on edit_state from the detail response.
  • Download Invoice PDF — a download URL for the invoice document.

All requests must be authenticated with your API Key. The gateway resolves your merchant identity from the API Key, so you do not send merchant_id in the body or query — it is ignored if present.

Base URL​

https://pay-openapi.morph.network/pro/merchant/payin/v1

Endpoint paths in this page are relative to that base URL.

Response envelope​

Every response is wrapped in a common envelope:

{
"error_code": 0,
"msg": "success",
"data": {},
"trace": "..."
}
note

Business success or failure is determined by error_code (error_code == 0 means success), not by the HTTP status code alone. See the Error Codes reference for the full list.

Validation rules​

Before a payment resource is created, the request is validated. If any check fails, the corresponding error code is returned and nothing is created:

  1. Receiving token & chain — (receiving_network, receiving_token_symbol, receiving_token_address) must match a supported, active entry in the Supported Tokens & Networks list. An unsupported chain returns 90111; a token that does not match the chain returns 90120.
  2. Receiving wallet — receiving_address must be a receiving wallet you have configured at https://account.morph.network/ under your merchant account, and it must be bound to receiving_network. An address that is not yours returns 90112; a wallet not bound to that chain returns 90111.
  3. Payer allowlist — support_networks and support_tokens limit which chains and tokens the customer can pick on Hosted Checkout. They come from Paying currencies, not from your receiving catalog.
    • Omit both (or send empty arrays) to allow every paying-catalog chain and token. The API expands the pair before create; pay-service never stores a blank allowlist.
    • Tokens only — support_networks is filled from the distinct support_tokens[].network values.
    • Networks only — support_tokens is filled from the paying catalog for those chains. A selected chain with no catalog token returns 90000.
    • Both set — forwarded as-is. Each token must match the paying catalog (90111 / 90120). The two fields must be a consistent pair (support_networks equals the distinct token networks).
POST /payment-links

Request fields​

FieldTypeRequiredDescription
amountstringYesPayment amount.
receiving_networkstringYesPrimary receiving chain.
receiving_token_symbolstringYesPrimary receiving token symbol.
receiving_token_addressstringYesPrimary receiving token contract address.
receiving_addressstringYesReceiving address — must be a wallet configured at account.morph.network and bound to the chain.
descriptionstringNoDescription.
namestringNoDisplay name. Omit to leave it unset. Invoices do not have this field.
return_urlstringNoHTTPS URL the Hosted Checkout success page uses for “Back to Merchant”. See Return URL.
support_networksstring[]NoCheckout chains. Omit with support_tokens to allow the full paying catalog.
support_tokensobject[]NoCheckout tokens (network / address / symbol). Must form a pair with support_networks when either is set.

Request example​

{
"amount": "100.00",
"receiving_network": "morph",
"receiving_token_symbol": "USDT",
"receiving_token_address": "0xe7cd86e13ac4309349f30b3435a9d337750fc82d",
"receiving_address": "0xdef2...7890",
"description": "July subscription",
"name": "July subscription",
"return_url": "https://merchant.example.com/paid",
"support_networks": ["morph"],
"support_tokens": [
{
"network": "morph",
"address": "0xe7cd86e13ac4309349f30b3435a9d337750fc82d",
"symbol": "USDT"
}
]
}

Success response​

{
"error_code": 0,
"msg": "success",
"data": {
"payin_id": "plink_...",
"payment_link_url": "https://s.short/abc",
"payment_link_long_url": "https://pay.example.com/checkout/eyJ...",
"status": "ACTIVE",
"name": "July subscription",
"description": "July subscription",
"receiving_source_type": "tee",
"return_url": "https://merchant.example.com/paid"
},
"trace": "..."
}

Create does not return edit_state or success_order_count. Those are on Get Payment Link. receiving_source_type is tee or external, taken from the receiving wallet you sent; the request body has no such field. Create always returns both checkout URLs. payment_link_long_url is {checkout base}/checkout/{token}. payment_link_url is the URL returned by the short-link service (data.shortUrl[0].url). Pay does not choose that host or path. The two URLs are equal only when the short-link service is unset or the call fails. Omitted name, description, and return_url come back as JSON null.

Create Invoice​

POST /invoices

An invoice takes every Payment Link field except name, plus the invoice-specific fields payer, due_date, and items.

Request fields​

FieldTypeRequiredDescription
amountstringYesPayment amount.
receiving_networkstringYesPrimary receiving chain.
receiving_token_symbolstringYesPrimary receiving token symbol.
receiving_token_addressstringYesPrimary receiving token contract address.
receiving_addressstringYesReceiving address — must be a wallet configured at account.morph.network and bound to the chain.
descriptionstringNoDescription.
support_networksstring[]NoCheckout chains. Omit with support_tokens to allow the full paying catalog.
support_tokensobject[]NoCheckout tokens (network / address / symbol). Must form a pair with support_networks when either is set.
payerobjectYesPayer information.
payer.namestringYesPayer name.
payer.emailstringNoValid email address.
due_dateint64YesInvoice due time (Unix millisecond timestamp).
itemsobject[]NoInvoice line items.
items[].namestringYesItem name.
items[].quantityint64YesQuantity, > 0.
items[].unit_pricestringYesUnit price.
reference_idstringNoYour own business reference for this invoice, up to 256 characters. Echoed back in the response and in the webhook payload for the resulting order.
return_urlstringNoSame as Create Payment Link. Not copied onto the order or the webhook.
note

Only name, quantity, and unit_price are read from each items[] entry; any other fields are discarded.

Request example​

{
"amount": "299.00",
"receiving_network": "morph",
"receiving_token_symbol": "USDT",
"receiving_token_address": "0xe7cd86e13ac4309349f30b3435a9d337750fc82d",
"receiving_address": "0xdef2...7890",
"description": "Annual plan",
"payer": {
"name": "Payer Name",
"email": "[email protected]"
},
"due_date": 1782777600000,
"items": [
{
"name": "Annual plan",
"quantity": 1,
"unit_price": "299.00"
}
],
"support_networks": ["morph"],
"support_tokens": [
{
"network": "morph",
"address": "0xe7cd86e13ac4309349f30b3435a9d337750fc82d",
"symbol": "USDT"
}
],
"reference_id": "your-ref-...",
"return_url": "https://merchant.example.com/paid"
}

Success response​

{
"error_code": 0,
"msg": "success",
"data": {
"invoice_id": "INV-...",
"checkout_url": "https://s.short/abc",
"checkout_long_url": "https://pay.example.com/checkout/eyJ...",
"due_date": 1782777600000,
"reference_id": "your-ref-...",
"receiving_source_type": "tee",
"return_url": "https://merchant.example.com/paid"
},
"trace": "..."
}

Create does not return status or edit_state. Those are on Get Invoice. Create always returns both checkout_url (short) and checkout_long_url ({checkout base}/checkout/{token}), same rules as a Payment Link. receiving_source_type is set the same way. An omitted return_url comes back as JSON null.

Return URL​

return_url is optional on create and update for both Payment Links and Invoices.

RuleBehavior
Omit, null, or "" on createThe resource is stored without a return URL. Create and detail return JSON null.
ValueMust be an https URL with a host, at most 2048 characters after trim. http, javascript:, ftp:, and values with no host return 90000.
UpdateOmit to keep the stored value. "" clears it.
CheckoutThe success page shows “Back to Merchant” and redirects there after 5 seconds. The URL is not rewritten and no order id is appended.
Order and webhookreturn_url is not copied onto the order and is not included in webhook payloads.

Edit state​

Get and successful update responses include data.edit_state. Create does not. Read it before you update; do not infer the lock from order counts yourself.

FieldDescription
codeFULLY_EDITABLE, PARTIALLY_LOCKED (Payment Link only), or FULLY_LOCKED (Invoice only).
editablefalse only when the invoice is fully locked.
editable_fieldsFields you may change. Empty array when nothing is editable.
locked_fieldsFields that must stay as stored.
lock_reasonnull when unlocked. Otherwise HAS_SUCCESS_ORDER, SUBMITTED_IN_FLIGHT, PAID, or EXPIRED.
can_duplicateAlways true. Duplicating is a new create, not an update.

Payment Link:

SituationcodeYou can changelock_reason
No successful order and no submitted in-flight orderFULLY_EDITABLEEvery fieldnull
At least one successful orderPARTIALLY_LOCKEDname, description, return_url, statusHAS_SUCCESS_ORDER
A submitted in-flight order, and no successful orderPARTIALLY_LOCKEDname, description, return_url, statusSUBMITTED_IN_FLIGHT

HAS_SUCCESS_ORDER wins over SUBMITTED_IN_FLIGHT.

Invoice is all or nothing:

Situationcodelock_reason
Unpaid, not expired, no submitted in-flight orderFULLY_EDITABLEnull
PaidFULLY_LOCKEDPAID
Submitted in-flight, and not paidFULLY_LOCKEDSUBMITTED_IN_FLIGHT
due_date is in the past, and not paid or in-flightFULLY_LOCKEDEXPIRED

PAID wins over SUBMITTED_IN_FLIGHT, which wins over EXPIRED. A locked invoice rejects the whole update with 90209.

GET /payment-links/detail?payin_id=<payin_id>

Pass payin_id as a query parameter. Do not put it in the path.

Query fields​

FieldTypeRequiredDescription
payin_idstringYesPayment Link id returned by create.

Success response​

data is the stored link: payin_id, merchant_id, amount, receiving fields, support_networks, support_tokens, name, description, return_url, payment_link_url (the short URL only; payment_link_long_url is not on this response), status (ACTIVE or INACTIVE), receiving_source_type (tee for a hosted wallet, external for your own receiving address), success_order_count, created_at, updated_at, and edit_state. return_url is JSON null when unset. An unknown id, including one that belongs to another merchant, returns 90201.

POST /payment-links/update

payin_id is required. Other fields are optional, with these rules:

  • Omit a field to keep the stored value.
  • name, description, and return_url: omit to keep, send "" to clear.
  • support_networks and support_tokens: omit to keep. Send [] to clear the allowlist (create would expand an empty allowlist; update does not).
  • While FULLY_EDITABLE, resend amount and every receiving_* from the detail. Omitting them returns 90000.
  • While PARTIALLY_LOCKED, omit the locked amount and receiving fields, or send the stored values unchanged. A different value returns 90209. name, description, return_url, and status can still change.
  • If receiving_network or receiving_address differs from the stored pair, the address is checked again (90112 if it is not your wallet, 90111 if it is not bound to that chain). Sending the stored pair skips that check.
  • If you send any of receiving_network, receiving_token_symbol, or receiving_token_address, send all three. An incomplete or unknown token returns 90120.

status here is ACTIVE or INACTIVE. Prefer Update Payment Link status when you only want to change status.

Request fields​

FieldTypeRequiredDescription
payin_idstringYesPayment Link id.
amountstringConditionalRequired on a fully editable link.
receiving_networkstringConditionalRequired on a fully editable link.
receiving_token_symbolstringConditionalRequired on a fully editable link.
receiving_token_addressstringConditionalRequired on a fully editable link.
receiving_addressstringConditionalRequired on a fully editable link.
namestringNoDisplay name. "" clears it.
descriptionstringNoDescription. "" clears it.
return_urlstringNoHTTPS return URL. "" clears it. See Return URL.
support_networksstring[]NoCheckout chains. [] clears the allowlist.
support_tokensobject[]NoCheckout tokens. [] clears the allowlist.
statusstringNoACTIVE or INACTIVE.

Request example​

Fully editable link, amount changed, receiving fields echoed:

{
"payin_id": "plink_...",
"amount": "0.02",
"receiving_network": "morph",
"receiving_token_symbol": "USDT",
"receiving_token_address": "0xe7cd86e13ac4309349f30b3435a9d337750fc82d",
"receiving_address": "0xdef2...7890",
"description": "amount-changed"
}

Success response​

data has the same shape as Get Payment Link, with the updated values and a fresh edit_state.

POST /payment-links/status

Changes only ACTIVE / INACTIVE. This stays allowed when the link is PARTIALLY_LOCKED. Any other status returns 90000.

Request fields​

FieldTypeRequiredDescription
payin_idstringYesPayment Link id.
statusstringYesACTIVE or INACTIVE.

Request example​

{
"payin_id": "plink_...",
"status": "INACTIVE"
}

Get Invoice​

GET /invoices/detail?invoice_id=<invoice_id>

Pass invoice_id as a query parameter.

Query fields​

FieldTypeRequiredDescription
invoice_idstringYesInvoice id returned by create.

Success response​

data is the stored invoice: invoice_id, merchant_id, amount, receiving fields, support_networks, support_tokens, description, payer, due_date, items, checkout_url (the short URL only; checkout_long_url is not on this response), reference_id, status (Pending, Paid, or Expired), receiving_source_type (tee or external), return_url, success_order_count (0 or 1), created_at, updated_at, and edit_state. Invoices have no name. return_url is JSON null when unset. An unknown id, including one that belongs to another merchant, returns 90201.

Update Invoice​

POST /invoices/update

The body mirrors Create Invoice, plus required invoice_id. While FULLY_EDITABLE you must resend amount, every receiving_*, payer.name, and due_date. Optional fields follow the same omit / "" / [] rules as Payment Link update.

due_date echoed from the detail skips the window check. A new due_date must be at least 5 minutes ahead and at most 90 days ahead; otherwise the update returns 90000 and the stored due time is unchanged.

A FULLY_LOCKED invoice returns 90209 for the whole request, including a description-only change. Receiving-address checks match Payment Link update.

Request fields​

FieldTypeRequiredDescription
invoice_idstringYesInvoice id.
amountstringYesPayment amount. Echo the stored value if you are not changing it.
receiving_networkstringYesPrimary receiving chain.
receiving_token_symbolstringYesPrimary receiving token symbol.
receiving_token_addressstringYesPrimary receiving token contract address.
receiving_addressstringYesReceiving address.
payerobjectYesPayer information.
payer.namestringYesPayer name.
payer.emailstringNoValid email address.
due_dateint64YesDue time in Unix milliseconds. Echo the stored value to leave it unchanged.
descriptionstringNoDescription. "" clears it.
return_urlstringNoHTTPS return URL. Omit to keep; "" clears it.
itemsobject[]NoLine items. Omit to keep; [] clears.
support_networksstring[]NoCheckout chains.
support_tokensobject[]NoCheckout tokens.
reference_idstringNoBusiness reference, up to 256 characters. "" clears it.

Request example​

{
"invoice_id": "INV-...",
"amount": "299.00",
"receiving_network": "morph",
"receiving_token_symbol": "USDT",
"receiving_token_address": "0xe7cd86e13ac4309349f30b3435a9d337750fc82d",
"receiving_address": "0xdef2...7890",
"payer": {
"name": "Payer Name",
"email": "[email protected]"
},
"due_date": 1782777600000,
"description": "after"
}

Success response​

data has the same shape as Get Invoice.

Download Invoice PDF​

GET /invoices/pdf?invoice_id=<invoice_id>

Renders one PDF for Pending, Paid, and Expired invoices and returns a download URL. The response is not the file itself. Pending and Expired documents show Order ID as --. A Paid document shows the successful payin order id.

Fetch download_url with a normal GET. The body starts with %PDF. file_name is <invoice_id>.pdf.

Missing invoice_id returns 90000. An unknown invoice returns 90201. A merchant profile without a name or login email returns 90102.

Query fields​

FieldTypeRequiredDescription
invoice_idstringYesInvoice id.

Success response​

{
"error_code": 0,
"msg": "success",
"data": {
"download_url": "https://...",
"file_name": "INV-....pdf"
},
"trace": "..."
}