Payment links
No app. No site. Still a real sale.
A payment link is the only thing you can ship with nothing behind it. Flint's is not a link to a charge: it is a sellable object with a type, a form, capacity and stock, and every buyer who pays leaves a real order.
{
"data": {
"payment_link_id": "plink_1kmn0aExample",
"status": "active",
"payment_link_type": "standard",
"url": "https://pay.withflintpay.com/plink_1kmn0aExample"
}
}One call, and the product is live. There is nothing else to deploy.
Share this and you are selling
01The four
One endpoint. Four products.
A link type is not a display option. Each one changes what the page collects, what the buyer gets, and what lands on the order.
A product with a price. Quantity and price can both be made adjustable, within bounds you set.
curl -X POST https://api.withflintpay.com/v1/payment-links \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Idempotency-Key: payment-link-spring-tee-001" \
-d '{
"name": "Limited spring tee",
"line_items": [
{ "name": "Spring tee", "quantity": 1,
"unit_price_money": { "amount": 3500, "currency": "USD" } }
]
}'- payment_link_type
- event_config
- plan_id
02The buyer
What they get to change.
Pay-what-you-want is easy to offer and easy to regret. Every buyer-facing control here has a floor and a ceiling you set.
{
"name": "Pay what you can",
"line_items": [
{
"name": "Workshop seat",
"quantity": 1,
"allow_quantity_adjustment": true,
"min_quantity": 1,
"max_quantity": 6,
"allow_unit_price_adjustment": true,
"min_unit_price_money": { "amount": 1000, "currency": "USD" },
"max_unit_price_money": { "amount": 20000, "currency": "USD" },
"suggested_unit_price_money_options": [
{ "amount": 2500, "currency": "USD" },
{ "amount": 5000, "currency": "USD" }
]
}
]
}Quantity and unit price are separately adjustable, each with a minimum and a maximum, plus suggested amounts the buyer can just click.
curl -X POST \
https://api.withflintpay.com/v1/payment-links/plink_1kmn0aExample/custom-fields \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"label": "Shirt size",
"custom_field_type": "dropdown",
"options": ["S", "M", "L", "XL"],
"required": true
}'Text, dropdown, checkbox and textarea, each optionally required. Answers arrive on the order with the payment.
- allow_unit_price_adjustment
- suggested_unit_price_money_options
- custom_field_type
03Availability
Three ways to stop selling.
A link keeps selling until you say otherwise, so the ways to say otherwise matter.
curl -X POST \
https://api.withflintpay.com/v1/payment-links/plink_1kmn0aExample/deactivate \
-H "Authorization: Bearer YOUR_API_KEY"Reactivating turns the same link back on, so a code already printed on something keeps working.
{
"name": "First 100 customers",
"max_completions": 100,
"inactive_message": "The spring drop has ended. Follow us for the next one."
}max_completions counts checkouts in flight as well as finished ones, so a rush of simultaneous buyers cannot overshoot a limited drop.
- max_completions
- completed_count
- inactive_message
04No integration
Print it and walk away.
The buyer-side call carries no API key at all.
# no API key on this one. it is the buyer-side call behind
# a code printed on a poster, a table tent, or a business card.
curl -X POST \
https://api.withflintpay.com/v1/payment-links/plink_1kmn0aExample/resolve \
-H "Content-Type: application/json" \
-d '{}'A poster, a table tent, or the back of a business card is a complete storefront.
A link is a URL
Nothing has to run to serve it. There is no callback to host, no key to rotate into a client, and no build to ship when the price changes.
- POST /v1/payment-links/{payment_link_id}/resolve
It is still a real sale
The absence of an integration does not downgrade what happens. The same order, the same webhooks, the same refunds as anything else you sell.
- order.paid
05Afterwards
One URL, one order each.
Many buyers share a link. None of them share a record.
{
"data": {
"order_id": "ord_1kmn0aExample",
"origin": "payment_link",
"status": "closed",
"payment_status": "paid",
"line_items": [
{ "name": "Spring tee", "quantity": 2,
"subtotal_money": { "amount": 7000, "currency": "USD" } }
],
"pricing_amounts": {
"total_money": { "amount": 7000, "currency": "USD" }
}
}
}origin says where it came from. Your metadata rides along, and the buyer's custom field answers arrive with it.
curl "https://api.withflintpay.com/v1/orders?origin=payment_link" \
-H "Authorization: Bearer YOUR_API_KEY"The filter is origin, not source.
Because it is an ordinary order
Refunds target its line items. Receipts render from it. Analytics counts it beside every other sale. None of that is special-cased for links, which is the reason it works at all.
Events
- order.paid
- subscription.activated
- origin
- metadata
- GET /v1/orders
06Limits
When to reach for something else.
A link is one URL for many buyers. When you need one page for one buyer, with an order you already built and a price nobody else should see, that is a checkout session. When you need terms, a due date and a document, that is an invoice. Which of the three to use is its own guide, and that guide owns the decision.
Capacity is enforced at payment, not at page open. An event link acquires its hold immediately before confirmation, so an older tab that has been sitting open can be turned away if someone else takes the last seats first. That is the right trade against overselling, and worth knowing before you promise a seat.
Flint is not a payment processor. Stripe processes every card, and Flint provisions and operates the processing account. You cannot attach a Stripe account you already have, and Flint inherits Stripe underwriting: if Stripe declined your business, Flint cannot approve it.
Against a link that only collects money
The link knows what it is selling, so donations, tickets and signups are types rather than workarounds.
payment_link_typeBuyers can move the price and the quantity, and cannot move either past your bounds.
min_unit_price_moneyA limited drop stops at the limit, counting checkouts already in flight.
max_completionsA retired link explains itself instead of erroring.
inactive_messageEvery buyer leaves a real order, filterable by where it came from.
origin=payment_linkReviewed 2026-07-25 against the published API.
07Start
Sell something in one call.
Node
CLI
Create a link, open its URL, and pay with 4242 4242 4242 4242. Then set max_completions to 1, pay it again, and watch the second buyer get told the drop is over. That is the whole product, and it takes about five minutes.
FAQ
Questions worth asking first.
What can a payment link actually sell?
Four different things, selected by payment_link_type. A standard link sells products with a price. A donation link presents an amount picker with suggested amounts and a floor. An event link sells ticket tiers against a shared capacity, with a date, a venue, and emailed tickets. Passing plan_id instead of line items turns the link into a hosted subscription signup.
Do I need a website to use one?
No, and that is the point. A link is a URL. The buyer-side resolve call carries no API key, so a code printed anywhere works with nothing running behind it. No app, no site, no integration, and what you get back is still a real order.
What happens after someone pays?
Every completed checkout creates its own order carrying origin payment_link, the link's metadata copied onto it, and the buyer's custom field answers. Refunds, receipts, webhooks and reporting work exactly as they do for any other order, because it is one. Filter them with GET /v1/orders and the origin parameter.
Can buyers change the price or quantity?
Only within bounds you set. allow_quantity_adjustment with min_quantity and max_quantity lets them pick how many. allow_unit_price_adjustment with min_unit_price_money and max_unit_price_money lets them pick what to pay, and suggested_unit_price_money_options gives them defaults to click. That covers pay-what-you-want, name-your-price and tiered giving without letting anyone pay a cent.
How do I stop a link, or cap it?
Three levers. Deactivate turns it off without breaking the URL, and buyers see your inactive_message instead of a generic error. max_completions caps how many checkouts can complete, counting in-flight ones too, so a burst of simultaneous buyers cannot blow past a limited drop. And expiration bounds how long one buyer's checkout stays open, which is per visit and not a campaign end date: to end a campaign, deactivate.
Are tickets real tickets?
Yes. An event link enforces max_total_quantity across all tiers, and capacity is acquired when payment starts rather than when a checkout opens, so an older idle tab cannot hold a seat. Buyers land on a ticket confirmation page, and with send_ticket_emails the tickets are emailed too. ticket_prefix brands the numbers, so GAL yields tickets like GAL042.