Create a payment link
POST /payment-links creates a checkout link that pays you — the owner of the API key. Pass an amount and an optional message; get back a URL to share.
Request
curl -X POST https://api.loofta.xyz/payment-links \
-H "Authorization: Bearer lft_live_..." \
-H "Content-Type: application/json" \
-d '{
"amount": 25,
"message": "Invoice #1042"
}'| Field | Type | Notes |
|---|---|---|
amount | number | USD, $10,000 maximum. $0.01 minimum for a plain public transfer (the default), $1 minimum if is_private is true. |
message | string, optional | Shown to the payer on the checkout page. 1000 characters max. |
is_private | boolean, optional | Defaults to false (plain public transfer). Pass true to require the payer to pay privately — shields the amount and that it went to your wallet on-chain, $1 minimum. |
webhook_url | string, optional | POSTed a signed "payment.succeeded" event once the payer's leg lands — see below. |
return_url | string, optional | Where the checkout page sends the buyer once paid. Baked into the returned url — works no matter how the link was opened (a plain link, QR code, or PayButton). |
Response
{
"short_id": "aB3xK9pQ",
"url": "https://pay.loofta.com/checkout/aB3xK9pQ",
"amount": 25,
"message": "Invoice #1042",
"webhook_secret": "a1b2c3..." // only present if you passed webhook_url — shown once
}Who pays the fees
The payer does. You always receive exactly amount — any fee is added on top of what they pay, never deducted from what you get. A private link's fee is 10 bps plus ~$0.20 flat gas; a public link (the default) has no privacy fee at all.
Knowing when it's paid
One link is one transaction — it can't be paid twice, so create a new link per order/customer rather than reusing one link for everyone. See onGenerateLink in the for the actual pattern: call this endpoint fresh, right when the buyer clicks "Buy now".
Save short_id against your own order record the moment the create response comes back, server-side — that's the only point you'll ever see it handed to you, and it's what you'll poll or match an incoming webhook against later. Two ways to find out it's done, pick whichever fits — you don't need both.
Option A — poll
No public endpoint needed on your side. Check whenever it's convenient — right after redirect, on a timer, whatever fits your flow:
curl https://api.loofta.xyz/payment-links/aB3xK9pQ \
-H "Authorization: Bearer lft_live_..."
{
"short_id": "aB3xK9pQ",
"status": "PRIVATE_TRANSFER_PENDING",
"paid": true,
"amount": 25,
"message": "Invoice #1042",
"paid_at": "2026-08-24T12:00:00.000Z"
}Only ever returns your own links — a short_id that isn't yours 404s. Check paid, not status, for "did the customer pay": it's true for both SUCCESS and PRIVATE_TRANSFER_PENDING. The latter just means your own claim/sweep of the funds into your wallet is still outstanding — that's a separate step you take in the Loofta app, unrelated to whether the order should be considered paid.
Option B — webhook
Pushed to you the moment it happens, no polling loop to run — better if you're fulfilling orders unattended. If you passed webhook_url, Loofta POSTs a signed event there the moment the customer's payment lands (same paid: true moment as above — not when you personally get around to claiming it):
POST <your webhook_url>
X-Loofta-Signature: sha256=<hmac>
{
"event": "payment.succeeded",
"short_id": "aB3xK9pQ",
"amount": 25,
"message": "Invoice #1042",
"paid_at": "2026-08-24T12:00:00.000Z"
}Verify it: recompute HMAC-SHA256(rawBody, webhook_secret) and compare against the header. If you rendered PayButton in a popup, its onSuccess also fires client-side as soon as the payer's own side of the payment lands — useful for instant UI feedback, but option A or B is what to trust for actually fulfilling an order.
Next: .