Skip to content

Payment links

Create a link with POST /v1/payment-links, the PaymentLinkCreate body and an Idempotency-Key. A link asks for a fixed amount, or for an open amount (amount_mode: OPEN) the payer chooses, optionally between minimum_amount and maximum_amount. Amounts are minor units in one currency (USD or CDF; CDF amounts are whole francs). usage is SINGLE (the link closes once paid) or MULTIPLE (it takes payments until it expires or is cancelled). expires_in_hours (1 to 720) is a duration: the server computes the expiry on its own clock. description and merchant_reference are required; return_url and branch_id are optional. return_url is an absolute https URL that each payment made through the link takes as its own, shown as « Retour à … » with nothing appended and never proof of payment; any other address answers 400 INVALID_REQUEST. A link takes no embedding, so its pages are never framed. Creating a link starts no payment. Retrying the same body and key returns the same link with its current state; a changed body conflicts.

Each link has a code (LK-XXXX-XXXX) that names it for people, support and lists; the code alone does not let anyone pay. List with GET /v1/payment-links (25 links, next_cursor, and counts: every link, the unpaid ones, and expiring, the links still payable and not yet expired whose expiry falls before the end of tomorrow in Kinshasa, on the server’s clock) and read one with GET /v1/payment-links/{id}, which also lists its latest payments. Both need payments:read and never carry the share URL. Reveal it with GET /v1/payment-links/{id}/share and payments:write: /p/{code}?token=… on the checkout is a bearer URL, so keep it out of logs, analytics and shared caches. The portal draws its QR locally and offers copy and user-controlled SMS/WhatsApp links; Futa sends no message in this sandbox.

A payer opens the link, enters the amount for an open link, and continues on the hosted checkout. Each attempt is an ordinary payment bound to the link’s terms, open for one hour and never after the link; it appears in the payments list and its webhooks as any payment. A single-use link has at most one attempt open, under way or approved: opening it again resumes that attempt, and a declined or expired attempt frees the link for another try. A multiple-use link accepts at most fifty open attempts at a time (429 LINK_BUSY).

Link statuses are UNPAID, OPEN (a multiple-use link that has received payments and takes more), PENDING and UNCERTAIN (an attempt under way: wait for its outcome before delivering or asking for another payment), PAID (a single-use link’s approved attempt), EXPIRED and CANCELLED. A declined attempt is the attempt’s outcome, not the link’s. Each link carries paid_count and paid_amount.

Cancel with POST /v1/payment-links/{id}/cancel and an idempotency key while the link is unpaid or open and no attempt is under way (409 LINK_NOT_CANCELLABLE otherwise). Its open attempts end, payers see it cancelled, and no funds move. Retries under the key return the link; a key used for another link conflicts.

Portal equivalents use /portal/merchants/{merchant}/payment-links, OIDC merchant identity and X-Futa-Membership-Version. Owner, Admin and Finance create, share and cancel links; Developer and Support read them. The browser BFF also enforces CSRF on POST; its cancellation request uses an empty JSON object. Portal reads of a payment never include its checkout_url.