Payment Link Operations: The Link Is Not the Payment, and It Is Not the Order
A payment link is a separate object from the payment and the order. Link state, expiry and revocation across Stripe, Adyen, Checkout.com and GOV.UK Pay.
A payment link is a third object alongside the payment and the order, with its own identifier, its own states and its own expiry rules where the provider defines any. The operational differences sit almost entirely in expiry, reuse and revocation defaults.
A payment link is a separate object with its own state machine — it is not the payment and it is not the order. Adyen gives the link four documented states (active, expired, completed, paymentPending), defaults it to one successful payment with a 24-hour expiry extendable to 70 days, and deletes the record after three months. Across the eight Stripe payment-link pages checked, no time-based expiry appears at all, and Stripe states a link cannot be deleted, only deactivated — reversibly, through the API at any time. Checkout.com rejects an already-used link at open time and expires links after 24 hours by default. All three separate link state from payment state, and Checkout.com tells merchants outright not to treat the front-end redirect as confirmation. Fulfil on payment webhooks, put your own reference on every link, and set expiry explicitly instead of inheriting a default.
A checkout session is created by your system when a customer decides to buy. It is born inside a conversation and dies with it — paid, abandoned, or timed out. A payment link is the opposite shape. You create it, hand it over, and it then exists on its own: in an email thread, a chat window, a printed invoice, a screenshot forwarded to someone who was never part of the conversation. It outlives the interaction that produced it, and almost every operational difference follows from that.
The consequence most integrations miss is that a link is a third object. Most systems model two — the payment and the order — and assume a link is a URL pointing at one of them. It is not. It has its own identifier, its own states, its own expiry rules where the provider defines any, and its own rules about who may use it and how often. Two providers document this from opposite directions. Adyen publishes a four-value state enum, one value of which exists only to say the link is finished but the money is not. Stripe publishes almost no link lifecycle — no expiry, no completion state — and gives you a counter and a reversible on/off switch instead. Neither is wrong; they are two answers to how much a link may know about a payment.
Scope note. This article covers the link object and its lifecycle. Merchant-reference discipline across providers is owned by the settlement-data ingestion and normalisation reference and is pointed at, not restated.
The Link Is Not the Payment
The three providers that document a link state model each support this from a different angle.
Adyen's state enum contains a state that only exists because the link cannot represent money. The link status field takes four values: active, "The link can be used to make payments."; expired, "The expiry date for the payment link has passed. Shoppers can no longer use the link to make payments."; completed, "The shopper completed the payment."; and paymentPending, "The shopper is in the process of making the payment. Applies to payment methods with an asynchronous flow." That fourth value is the tell. If link state and payment state were the same thing, three would do. It exists because a shopper can finish everything the link can observe while the money is still in flight.
Stripe advances its counter on session completion, not on settled funds. Its customisation guide is explicit: "A payment link is considered “paid for” when a Checkout Session is complete." The same page's API section ties that phrase to the moment Stripe sends the checkout.session.completed webhook event. Neither is about money arriving. Stripe's fulfilment guide, whose contents apply to Payment Links as well as Checkout "unless otherwise noted", carries the correction: delayed payment methods generate a later checkout.session.async_payment_succeeded event, and "The status of the object is in processing until the payment status either succeeds or fails." Stripe's sample fulfilment function gates on the Checkout Session's payment_status property — not on anything about the link.
Checkout.com states the operator rule directly: "Do not rely only on the front-end redirection as confirmation that the payment was successful. Confirm the payment status from the relevant webhooks before you finalize the order or fulfill the sale." Its status vocabulary makes the same point from the other end — Active explicitly covers the case where "A payment may have been attempted by the customer but not completed successfully." A link showing Active is not a link nobody has touched.
The conclusion is common to the three providers that document link state, and not subtle: fulfil on payment webhooks. Never on link state, and never on the redirect. Stripe puts the reason plainly — "You can’t rely on triggering fulfilment only from your checkout landing page, because it’s not guaranteed customers visit that page." A link makes that worse than an on-site checkout, because whoever opens it is often on a device you have no relationship with. The GOV.UK Pay pages cited here do not discuss link state or the redirect at all; they direct you to check payment status through the API on return — the same destination by a different route.
The Link Is Not the Order
The second identifier problem is quieter, and surfaces later in reconciliation.
The three providers with a documented link API each hand you at least two identifiers, and neither is your order. Checkout.com's paid-link response carries a payment_id distinct from the pl_ link id. Adyen returns a link id and separately a pspReference. Stripe returns a plink_ id and creates Checkout Sessions from the link — its metadata documentation describes sessions, plural, "created by this payment link". In every case the tie back to an order is something you supplied, or it does not exist.
Adyen makes the requirement structural: its create guide lists reference among the three parameters a POST to /paymentLinks must specify, alongside merchantAccount and amount, and defines it as "A reference that is used to uniquely identify the payment in future communications about the payment status." It also closes off the obvious workaround. Metadata allows a "Maximum 20 key-value pairs per request." with a "Maximum 20 characters per key.", and one key is taken: "A key cannot have the name checkout.linkId. Any value that you provide with this key is going to be replaced by the real payment link ID." The one metadata slot an integration might use for the link id is reserved and silently overwritten. Stripe copies link metadata onto the Checkout Sessions the link creates, which helps only if it identifies the order. GOV.UK Pay puts the reference in the URL itself — "Prefill these details by adding reference and amount information to your payment link URL using query parameters." — with letters and numbers permitted, an underscore encoding a space, and the amount in pence.
The merchant-reference argument belongs to the settlement-data ingestion reference linked above. What the link adds is a second identifier, plus a warning: the id you most want to key on is often the one the provider reserves.
Expiry and Revocation: Where Providers Genuinely Diverge
Everything above is common structure. This is where the four actually diverge, and where a mental model carried from one to another produces incidents.
| Behaviour | Stripe | Adyen | Checkout.com | GOV.UK Pay |
|---|---|---|---|---|
| Default expiry | None documented across eight pages checked | 24 hours | 24 hours | None documented for the link |
| Maximum life | Not documented | 70 days from creation | Not documented | Not documented |
| Early-expiry trigger | Reaching a configured completed-sessions limit | Five unsuccessful payment attempts | Not documented | None for the link; a payment created by the Create-a-payment API call expires after 90 minutes |
| Single-use by default | No — no default cap | Yes, one successful payment | Yes — a used link is rejected at open time | No single-use restriction documented (reuse is inferred from the prefill design) |
| Force expiry or revoke | Deactivate by setting active to false, or hit a completed-sessions limit | PATCH the link with status: expired | Not documented on the pages used here | Not documented on the pages used here |
| Reversible? | Yes — reactivate through the API at any time | Not documented | Not documented | Not documented |
| Deletion | Documented as impossible — a link cannot be deleted | Not documented as a merchant operation | Not documented | Not documented |
| Link state model | active boolean plus a completed-sessions counter | Four-value status enum | Three statuses | Not documented as a link state model |
| Record retention | Not documented (and since deletion is documented as impossible, the object presumably persists — inference) | Details kept three months from creation | Not documented | Not documented |
Three of those columns deserve expanding.
Stripe documents no time-based expiry, and no way to delete a link. This is a statement about the documentation, not a criticism. Eight Stripe payment-link pages were checked — the overview, the creation, sharing, post-payment and customisation guides, and the create, update and object API references — and no expiry parameter, default or maximum appears on any. The only two uses of the word sit on the sharing guide, and neither is a clock: both name an "expiry page" that a deactivated link redirects to — "The QR code doesn’t expire. If you deactivate the underlying payment link, the QR code redirects to an expiry page."
Deactivation is the only retirement mechanism, and Stripe says so plainly: "After you create a payment link, you can’t delete it." The switch is the active boolean — "Whether the payment link’s url is active. If false, customers visiting the URL will be shown a page saying that the link has been deactivated." — with inactive_message capped at "The maximum length is 500 characters." And it runs both ways: setting active back to true reuses a deactivated link, because "You can reactivate the payment link through the Payment Links API at any time."
You can also arm an automatic cutoff. restrictions itself is optional — which is why there is no default cap — but limit is required once you use it: restrictions.completed_sessions.limit is "The maximum number of checkout sessions that can be completed for the completed_sessions restriction to be met", with a read-side counter, "The current number of checkout sessions that have been completed on the payment link which count towards the completed_sessions restriction to be met". Once it trips, "When the payment link reaches the limit, it automatically deactivates and customers can’t use it to make a purchase." The consequence: a Stripe link created for one invoice in March is, by default, still payable in November — and deactivating it leaves an object that can be switched back on. That is a governance question about who creates links and who can reactivate them.
Adyen expires links aggressively and deletes the records. Its create guidance: "By default, payment links expire after 24 hours. You can extend the expiry of the payment link to a maximum of 70 days from the time you create it. If a shopper makes five unsuccessful payment attempts using the same payment link, the payment link expires before this timeframe." The expiresAt field reference repeats it — "The maximum expiry date is 70 days after the payment link is created." and "If not provided, the payment link expires 24 hours after it was created." Revocation is documented rather than inferred: a PATCH setting status to expired. Adyen adds that "Changing the status to expired also changes the payment link’s expiry date to the current date and time." Then a retention limit most integrations meet during a dispute: "We keep payment link details for three months. If it was more than three months ago that a payment link was created, the details are no longer available." The overview page repeats it — "Payment links are removed automatically three months after they have been created."
Side by side, those two are opposite permanence models, not a present default and a missing one. Adyen's record disappears on a clock you do not control, three months after creation, paid or not. Stripe's cannot be removed at all — only switched off, and switched back on at any time. One artefact expires out from under you; the other persists until someone acts.
Checkout.com blocks reuse at the point of opening. The guard is a check the provider performs, not an inference from the product's scope: "We check that the link has not already been used or expired, and then load the payment page." Its create page separately scopes what a link is for — "You can only use links to accept one-time payments, not recurring payments.", which contrasts one-off payments with subscriptions rather than describing single use — and sets the clock: "Links expire after 24 hours by default." No revocation or deactivation operation appears on either page retrieved here — a statement about those pages, not a claim that revocation is impossible.
One constraint changes front-end designs at two of the four: Adyen states that "Starting November 1, 2025, you cannot embed payment links in an iframe." and Checkout.com that "You cannot embed Payment Links in iframes."
Duplicate Exposure — PaymentBrief's Framing, Not the Providers'
None of the provider documentation cited here discusses duplicate-payment exposure in operator terms. The framing is PaymentBrief's synthesis of what each provider builds, not something any of them says. What they build differs materially:
- Adyen documents the guard. "By default, payment links can only be used to make one successful payment." — and after a successful payment, anyone opening the same link sees a You have already paid message.
- Stripe documents the countermeasure but not a default.
restrictions.completed_sessions.limitdoes exactly the right thing, but you have to set it. A link created without it has no cap. - Checkout.com performs the check itself, before the page loads, as quoted above.
- GOV.UK Pay documents no single-use restriction. Its prefill guidance describes one admin-created link shared repeatedly, the reference varying per recipient in the query string — so reuse is read from the design, not from a stated rule.
None is wrong — a donations link and a single invoice link want opposite defaults. The point is that a link, unlike a checkout session, is forwardable, and the forwarded copy is indistinguishable from the original. Whether a second person paying it produces a second payment is a per-provider, per-configuration property to look up rather than assume.
Two Things the Documentation Does Not Answer
Neither is settled by the pages used here. Confirm both with your provider before designing around either.
- Does an Adyen reusable link stay
activeafter its first successful payment? Adyen gives a derivation rule — "If the link is not expired and has not been paid, the status is active. Else, the status is completed or expired." — but it is written for the ordinary single-use link and says nothing about the reusable case, where a link can be both paid and still usable. If such a link moves tocompletedafter payment one, monitoring that treatscompletedas terminal will misread a link that is still collecting. - Does Stripe reduce
completed_sessions.countwhen an async payment later fails? The counter advances on session completion, and a delayed method can complete a session and then fail. Whether it is reduced — and so whether a link that auto-deactivated at its limit holds a slot against money that never arrived — is not documented on the object or create pages.
Neither is a criticism. They are the two questions to put to an account manager before treating a link counter as an inventory control.
Refunds and Part-Payments
Refunds are documented by one provider of the four. Stripe's post-payment guide covers them as a Dashboard flow on the payment, not a link-level operation: "By default, you issue a full refund. For a partial refund, enter a different refund amount." Bulk refunding carries a restriction worth knowing before a mass reversal — "You can only issue full refunds in this way; partial refunds must be issued individually." — and "It typically takes 5-10 business days for the refund to be processed and reflected on the customer’s bank statement."
The Adyen, Checkout.com and GOV.UK Pay pages cited here do not address refunds of link-collected payments at all — so whether the link record is involved, and what happens once Adyen's three-month window closes, are unanswered for those three. Check your provider's own refund documentation rather than generalising from Stripe's.
Part-payments are documented as unsupported, which is stronger than an absence: Stripe's Invoicing-versus-Payment-Links comparison lists "Collect partial payments or set up payment plans" as supported by Invoicing and not by Payment Links. Its customer-chooses-what-to-pay model — optional preset, optional minimum and maximum, default maximum 10,000.00 USD — is a pricing model for tips and donations, carried as a separate row in the same table. A link that lets the payer name an amount is not one collecting an instalment against a fixed amount.
Operator Checklist
- Model the link as its own object with its own id, alongside the payment and the order. Store all three.
- Fulfil on payment webhooks only. Link state and the redirect are both insufficient, and the three providers that document link state say so in their own words.
- Set expiry explicitly per provider — the documented defaults range from 24 hours to none at all.
- Set a usage cap where the provider offers one, and know the default when it does not.
- Put your own unique reference on every link, and check which metadata keys the provider has reserved.
- Decide who may create links, who may retire them, and where deactivation is reversible, who may switch one back on.
- Archive link-level evidence yourself. Adyen's three-month retention caps what the provider holds; Stripe's non-deletable links set the opposite problem.
- Confirm the two open questions above before treating a link counter or a
completedstatus as authoritative.
Related References
- Settlement Data Ingestion and Normalisation Across Multiple PSPs — the merchant-reference discipline link-collected payments depend on.
- PSP Reconciliation Failure Runbook — when the identifiers do not tie out.
- Refund Operations Runbook — refund mechanics in depth, including the path Stripe documents from the Dashboard.
- Stripe vs Adyen vs Checkout.com: 2026 Pricing Teardown — the same providers on commercials rather than lifecycle.
Sources & methodology (18)
restrictions is an optional object on the Stripe payment link create call; within it, restrictions.completed_sessions.limit is a required integer, defined as the maximum number of checkout sessions that can be completed for the completed_sessions restriction to be met
Defines the completed-sessions limit parameter, quoted verbatim; establishes that restrictions is optional while limit is required within it. No expiry parameter appears in the page text used here.
Checked:
The Payment Link object exposes restrictions.completed_sessions.count, the current number of checkout sessions completed on the link that count towards the restriction
Defines the read-side completed-sessions counter, quoted verbatim. Does not state whether the counter is reduced if a completed session's payment later fails.
Checked:
The payment link active boolean controls whether the link's URL is usable, and inactive_message sets the message shown when a link is no longer active, to a maximum of 500 characters
Defines the active boolean and the inactive_message field with its 500-character cap, both quoted verbatim, and establishes that link metadata is copied to the Checkout Sessions the link creates. No expiry parameter appears in the page text used here.
Checked:
A payment link is considered paid for when a Checkout Session is complete; when a link reaches its configured limit it automatically deactivates and can no longer be used to make a purchase
Establishes when a link counts as paid for, and that reaching a configured limit deactivates the link automatically; both sentences quoted verbatim. Documents no link expiry.
Checked:
Stripe's Payment Links pricing models include one where customers choose what to pay, with an optional preset amount and optional minimum and maximum amounts; the default maximum payment amount is 10,000.00 USD
Establishes that a customer-chosen amount is a pricing model with an optional preset and optional minimum and maximum, not a part-payment of a fixed amount. Documents no link expiry.
Checked:
Stripe's Invoicing-versus-Payment-Links comparison lists collecting partial payments or setting up payment plans as supported by Invoicing and not supported by Payment Links, and lists no-code refunds for Payment Links as available; the same page documents no expiry, expiry default or expiry parameter for a payment link
Establishes that part-payments are affirmatively unsupported for Payment Links rather than merely undocumented, and corroborates that refunds of link-collected payments are documented. It contributes to the eight-page expiry sweep as a page carrying no expiry statement.
Checked:
Stripe documents refunding link-collected payments from the Dashboard — full by default, partial by entering a different amount, bulk refunds restricted to full refunds only, and a typical 5-10 business day settlement to the customer's statement; the same page documents no expiry, expiry default or expiry parameter for a payment link
Establishes that refunds of link-collected payments are documented for Stripe, as a Dashboard operation on the payment rather than on the link; the full/partial, bulk-restriction and timing sentences are quoted verbatim. It contributes to the eight-page expiry sweep as a page carrying no expiry statement.
Checked:
A Stripe payment link cannot be deleted after creation; it can only be deactivated by setting the active attribute to false, and a deactivated link can be reactivated through the Payment Links API at any time, with customers redirected to an expiry page in the meantime
Establishes that a payment link cannot be deleted after creation, only deactivated, and that deactivation is reversible through the API at any time; all three statements quoted verbatim. Carries the only two uses of the word expiry across the eight Stripe payment-link pages checked, both referring to the page a deactivated link redirects to rather than to any time-based expiry of the link.
Checked:
Webhooks are required for fulfilment because a customer is not guaranteed to reach the landing page; delayed payment methods generate a later async_payment_succeeded event and the object remains in processing until the payment status succeeds or fails; the sample fulfilment function checks the Checkout Session payment_status
Establishes that webhooks are required for fulfilment and that a delayed payment method leaves the object in processing until the payment status resolves; both quoted verbatim. States that its contents apply to Payment Links as well as Checkout unless otherwise noted.
Checked:
Adyen payment links expire after 24 hours by default, may be extended to a maximum of 70 days, expire early after five unsuccessful payment attempts, and default to one successful payment per link, with a message shown to anyone opening a link that has already been paid
Establishes the 24-hour default, 70-day maximum, five-failed-attempt early expiry and one-successful-payment default, all quoted verbatim. Does not state whether a reusable link remains active after its first successful payment.
Checked:
A POST to /paymentLinks must specify merchantAccount, reference and amount; forcing expiry is a PATCH request that sets status to expired, which also moves the link's expiry date to the current date and time; payment link details are kept for three months; payment links cannot be embedded in an iframe from November 1, 2025; a link that is not expired and not paid has status active, otherwise completed or expired
Establishes the force-expiry operation and its effect on the expiry date, the three-month retention limit, the iframe prohibition and its effective date, all quoted verbatim, and lists reference among the parameters a create request must specify.
Checked:
The Adyen payment link status enum is active, expired, completed and paymentPending, the last covering payment methods with an asynchronous flow; reference is defined as the identifier used to uniquely identify the payment in future communications; metadata is capped at 20 key-value pairs with 20-character keys and cannot use the reserved key checkout.linkId; reusable defaults to false; expiresAt defaults to 24 hours and maxima at 70 days
Defines the four link status values, the reserved metadata key, the metadata limits and the reusable and expiresAt defaults, all quoted verbatim. Its Required column marks presence in the response, not obligation in a create request.
Checked:
Adyen removes payment links automatically three months after they have been created, and positions Pay by Link as a supplemental acceptance channel rather than a merchant's primary one
Corroborates the three-month retention limit from the product side, quoted verbatim, and positions Pay by Link as a supplemental acceptance channel rather than a merchant's primary one.
Checked:
Checkout.com Payment Links accept one-time payments only, expire after 24 hours by default, cannot be embedded in iframes, and are checked at open time for prior use or expiry before the payment page loads
Establishes that a used or expired link is rejected before the payment page loads, that links are scoped to one-time rather than recurring payments, the 24-hour default expiry and the iframe prohibition, all quoted verbatim.
Checked:
Checkout.com documents three Payment Link statuses (Active, Payment Received, Expired), warns against treating the front-end redirection as confirmation of a successful payment, and returns a separate payment_id and payment link once a link has been paid
Defines the three link statuses and warns against treating the front-end redirect as confirmation, both quoted verbatim, and shows a separate payment_id on a paid link. No revocation, cancellation or deactivation operation appears on this page or on the create page.
Checked:
GOV.UK Pay payment links are created in the admin tool and prefilled by appending reference and amount query parameters to the link URL, with the amount given in pence and letters and numbers permitted in the reference
Establishes how a link is prefilled by query parameters, quoted verbatim. Documents no expiry for the link itself and no single-use restriction, so reuse is inferred from the design rather than stated.
Checked:
In the GOV.UK Pay payment journey, a user has 90 minutes to complete a payment once it has been created; an incomplete payment expires, a cancellation is sent to the payment provider, and a P0020 error code is returned when the payment status is checked
Establishes a 90-minute completion window attaching to a payment created by a Create-a-payment API request, not to a shareable link. Says nothing about link state or the post-payment redirect.
Checked:
The framing of duplicate-payment exposure as an operator risk, the four-provider comparison structure, and the operational guidance drawn from the cited documentation are PaymentBrief synthesis rather than provider statements
None of the provider documentation cited here discusses duplicate-payment exposure in operator terms; the framing is PaymentBrief's and is labelled as such in the body.
Checked:
Source types explained in our Methodology.