Integration guide
Create a payment link on your server, redirect the customer to it, and confirm the payment before you fulfill the order. The API is at https://mantepay.com. It takes no API key.
Create a payment link
POST /api/sessions
| Parameter | Required | Description |
|---|---|---|
merchant | Yes | The address that receives the USDC payout. |
amountCents | Yes | The amount to charge, in US cents. See amounts and fees. |
returnUrl | Yes | Where the payment page sends the customer after they pay. https only (http for localhost), at most 1024 characters, no credentials. |
maxFeeBps | No | The highest fee you accept, in basis points. Defaults to 1000 (10%). The maximum is 3000. |
expiresIn | No | Seconds until the link expires. Defaults to 3600. Between 60 and 2592000. |
const res = await fetch("https://mantepay.com/api/sessions", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
merchant: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
amountCents: 2500,
returnUrl: "https://shop.example/thanks?order=42",
}),
});
const { id, url } = await res.json();
The response is 201:
{
"v": 1,
"id": "0x4f1c…e9a2",
"url": "https://mantepay.com/pay#AnCZeXDF…",
"merchant": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"amountCents": 2500,
"maxFeeBps": 1000,
"expiresAt": 1800003600,
"returnUrl": "https://shop.example/thanks?order=42"
}
id identifies the payment. Store it with the order. url is the payment page. expiresAt is in Unix seconds.
Redirect the customer
Send the customer to url. A link can be paid once and stops working at expiresAt. If an attempt fails, the customer can try again on the same link.
After a successful payment the page shows a button to returnUrl. It does not redirect automatically, and the customer may not return.
Do not treat a visit to returnUrl as proof of payment. Check the payment status, as below, before you fulfill the order.
Check the payment status
GET /api/sessions/{id}
id is the value returned when you created the link.
const res = await fetch(`https://mantepay.com/api/sessions/${id}`);
const payment = await res.json();
if (payment.status === "paid") {
// fulfill the order
}
The response for a paid payment:
{
"v": 1,
"id": "0x4f1c…e9a2",
"status": "paid",
"merchant": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
"amountCents": 2500,
"payout": "24.25",
"paidAt": 1800000300
}
| Status | Description |
|---|---|
pending | Not paid. This includes a customer who is still entering their card details, and one whose last attempt failed and who can try again until the link expires. |
processing | A payment is being processed. Check again shortly. |
paid | Paid. The USDC has been sent to merchant. |
merchant, amountCents, payout and paidAt are present once the status is paid. payout is the USDC you received, after the fee, and paidAt is in Unix seconds.
There are no webhooks. Poll this endpoint, for example when the customer reaches your returnUrl, and until expiresAt for orders that are still waiting.
Amounts and fees
- Amounts are in US dollars and are paid out in USDC.
- There is a minimum and a maximum amount. A request outside them is rejected with
400and the range, for exampleamountCents: must be between 1000 and 50000. - Payments are funded by independent acquirers, who keep a fee from the amount. You receive the amount less that fee, which never exceeds
maxFeeBps. At the default of 10% a $25.00 payment pays at least $22.50. At 3% it pays 24.25 USDC. - If no acquirer will fund a payment at your
maxFeeBps, the customer's card is not used and the link stayspending.
What the customer sees
The payment page shows the amount and the merchant address, then the card form. Card details are encrypted in the customer's browser. Above the card fields the page states:
When you pay, your card is used to buy a $25.00 Sephora e-gift card. That purchase is how the merchant is paid. The gift card is not issued to you. Your card statement will show the retailer's name, not the merchant's.
Testing
In test mode the payment page says so, no retailer is contacted and nothing is charged. Use any future expiry date and any three-digit security code. Do not enter a real card. The demo shop creates a payment link, sends you to pay it and checks the payment status when you come back.
| Card number | Result |
|---|---|
4242 4242 4242 4242 | Succeeds. |
4000 0000 0000 0002 | Declined. |
4000 0000 0000 3220 | Requires 3-D Secure. |
Errors
A failed request returns {"error": "…"} with one of these statuses.
| Status | Description |
|---|---|
400 | The request is invalid. The message begins with the parameter, for example returnUrl: must be an https address. |
413 | The request body is larger than 32 KB. |
429 | Rate limit exceeded: 60 link requests and 120 status requests a minute, across all clients. |
503 | Temporarily unavailable. Retry shortly. |