Skip to content

Payment Links

A payment link lets you create a shareable URL for a single payment request without building Card Handoff integration. The buyer opens this URL in their own browser, picks an installment count (if offered), and completes the payment themselves; your checkout does not collect the card. It is an alternative to the POST /v1/payments + handoff flow, useful when you want to avoid the card surface entirely (for example, sharing an order by phone or message).

POST /v1/payment-links
Authorization: Bearer ldg_live_…
Content-Type: application/json
{
"amount": 125050,
"terminalId": "trm_3Kd9x",
"customerCommissionShareBps": 0,
"description": "Order #8841",
"redirectUrl": "https://yourstore.example/payment"
}
Field Required Description
amount Yes The base amount you are requesting, integer in minor units (kuruş) (1,250.50 TRY → 125050). See Amounts and Currency.
terminalId Yes The terminal the payment is routed to. Must belong to you and be active; otherwise terminal_not_allowed is returned.
customerCommissionShareBps No The share of the commission passed on to the buyer, integer basis points (0-10000; 2.5% → 250). Defaults to 0 (you absorb the entire commission).
description No Link description.
redirectUrl No The base address the buyer is redirected to after the payment is settled; validated at creation time (see redirectUrl behavior).
allowedInstallmentCounts No The list of installment counts accepted for this link. If omitted (or null), there is no restriction and your terminal’s effective installment set (Installment Options) applies. If you supply it, the list cannot be empty and must be a subset of your terminal’s effective installment set; otherwise the request is rejected with 422 installment_not_allowed. This restriction cannot be changed once the link is created.
items No Display items listed above the “Total” line on the payment page. For the shape, the total-matching rule, and a sterility caution, see Display items and payer name.
payerName No The buyer’s name; you supply this field, the buyer does not enter it. Up to 120 characters. Shown masked on the payment page; see Display items and payer name.

201 Created:

{
"id": "01J9K5QATN6M1PXG7VYSD3HZWB",
"token": "plk_EXAMPLE0000000000…",
"url": "https://pay.lydiagate.example/link/plk_EXAMPLE0000000000…",
"partnerReference": 42,
"status": "pending",
"amount": 125050,
"customerCommissionShareBps": 0,
"description": "Order #8841",
"redirectUrl": "https://yourstore.example/payment",
"firstOpenedAt": null,
"lastOpenedAt": null,
"expiresAt": "2026-06-21T12:00:00Z",
"createdAt": "2026-06-14T12:00:00Z",
"updatedAt": "2026-06-14T12:00:00Z",
"allowedInstallmentCounts": null,
"items": null,
"payerName": null
}
Field Description
id The link’s permanent identifier; used in the single-item query, cancel calls, and the paymentLinkId field in the webhook body.
token The secret component of url. You do not need to store it separately; url already contains it.
url The full address you share with the buyer.
partnerReference An increasing sequence number scoped to your account (for display/reference only).
status The link’s current status. See Lifecycle.
amount The base amount you requested (kuruş).
firstOpenedAt / lastOpenedAt The instant the buyer first/last opened the link; null if not opened yet.
expiresAt The link’s expiry instant. See Window (TTL).
createdAt / updatedAt Timestamps (UTC).
allowedInstallmentCounts The installment restriction you sent in the request, sorted ascending; null if there is no restriction.
items The display items you sent in the request, in the same order; null if you did not send any.
payerName The payer name you sent in the request, in raw form; null if you did not send one. The payment page shows the masked form instead; see Display items and payer name.
Status Meaning
pending The link was created; the buyer has not opened it yet.
launched The buyer opened the link.
initiated A payment attempt is in 3D Secure; in flight. No new attempt can be started while in this status. If the attempt fails without completing, the link becomes payable again.
completed The payment was completed (terminal — no further transition).
expired The window (TTL) elapsed; the link became invalid unpaid (terminal).
failed A payment attempt failed; not terminal — the link can be paid again.
cancelled You cancelled it (terminal).

A link’s default validity window is 7 days from creation. Once expiresAt has passed, the link moves to expired and can no longer be paid.

GET /v1/payment-links?status=pending&status=launched&page=0&size=20
Authorization: Bearer ldg_live_…
Parameter Description
status Filter by one or more statuses; repeat the parameter (status=pending&status=launched).
minAmount / maxAmount Amount range (kuruş).
createdFrom / createdTo Creation time range (UTC, ISO 8601).
page / size Pagination; defaults are page=0, size=20. Out-of-range values are corrected silently: a negative page becomes 0 and size is clamped to 1..200 (e.g. size=500 → 200). The response always echoes the applied page/size. The one hard bound is page × size ≤ 100000; exceeding it returns 400 validation_error (no such deep page exists).

Response (200 OK):

{
"items": [
{
"id": "01J9K5QATN6M1PXG7VYSD3HZWB",
"token": "plk_EXAMPLE0000000000…",
"url": "https://pay.lydiagate.example/link/plk_EXAMPLE0000000000…",
"partnerReference": 42,
"status": "pending",
"amount": 125050,
"customerCommissionShareBps": 0,
"description": "Order #8841",
"redirectUrl": "https://yourstore.example/payment",
"firstOpenedAt": null,
"lastOpenedAt": null,
"expiresAt": "2026-06-21T12:00:00Z",
"createdAt": "2026-06-14T12:00:00Z",
"updatedAt": "2026-06-14T12:00:00Z",
"allowedInstallmentCounts": null,
"items": null,
"payerName": null
}
],
"page": 0,
"size": 20,
"hasMore": false
}
Field Description
items The links on this page; each item carries the same fields as the creation response. ⚠ This outer envelope’s items is the list of links; each link’s own display items live in the items field inside that link’s object (see Display items and payer name) — the two fields share a name but mean different things.
hasMore true when the page is full (at the applied size) and the next page’s offset remains within the 100000 safety horizon. Increment page only when this is true; a full page at the horizon returns false.
GET /v1/payment-links/{id}
Authorization: Bearer ldg_live_…

{id} is the id field from the creation or listing response (not token).

DELETE /v1/payment-links/{id}
Authorization: Bearer ldg_live_…

Only links in a non-terminal status (pending / launched / initiated / failed) can be cancelled; a successful call moves the link to cancelled and returns the updated link record. Trying to cancel a link that has already completed, expired, or was already cancelled fails (see below).

The public payment-link endpoints use two separate API-key budgets:

  • POST /v1/payment-links and DELETE /v1/payment-links/{id} share a 30/minute create/cancel budget.
  • Single-item GET /v1/payment-links/{id} and list GET /v1/payment-links share a 120/minute read budget.

All four endpoints also share a 120/minute limit for the same source IP. Different API keys under the same account have independent API-key budgets, but they still share the IP budget when requests come from the same source IP. Read usage does not consume the create/cancel budget, and create/cancel usage does not consume the read budget.

When a limit is exceeded, Lydia Gate returns 429 rate_limited before reading, creating, or changing a link. The Retry-After: 60 response header gives the wait time in seconds.

Before these budgets, all API-key /v1 endpoints pass through a coarse abuse-control gate before the key is looked up: a shared 240/minute budget for the same source IP and a 600/minute platform-wide budget. This upstream protection is not a payment-link business quota. Different /v1 endpoints share its IP budget, so abnormal aggregate load can produce an earlier 429 rate_limited response. For a clean source, the 240/minute ceiling does not shadow the payment-link IP budget of 120/minute.

If you supply a redirectUrl, Lydia Gate validates it at link creation time:

  • The address must be an absolute URL with the http or https scheme; otherwise the request is rejected with 400 validation_error.
  • If your account has an allowed host list configured, the redirectUrl host must appear on that list; a host that is not on the list is rejected the same way, with 400 validation_error. If no such list is configured, only the scheme is checked. This is the same open-redirect protection as the returnUrl validation on POST /v1/payments.

Once redirectUrl passes validation, after the buyer sees the payment result, they are redirected to:

  • On a completed payment → {redirectUrl}/payment/success,
  • On a failed attempt → {redirectUrl}/payment/failure.

If you do not send a redirectUrl, the buyer sees the result on a Lydia Gate-hosted page.

items and payerName are extra information shown on the payment page; both are optional, and neither one enters the amount calculation or the request sent to the bank.

If you send items, their amounts must add up to exactly the amount field:

{
"amount": 125050,
"terminalId": "trm_3Kd9x",
"items": [
{ "name": "Annual membership", "amount": 100000 },
{ "name": "Shipping", "note": "Standard delivery", "amount": 25050 }
]
}
Field Required Description
items[].name Yes Item name, up to 120 characters.
items[].note No A subtitle for the item, up to 200 characters.
items[].amount Yes Item amount, integer in minor units (kuruş), at least 1.

If the total does not match amount, the request is rejected with 422 payment_link_items_amount_mismatch. Partial itemization is not supported: if you cannot send every item in the order, do not send items at all. The order you send is preserved; items are listed on the payment page in the same order. The list can hold 1 to 50 items; an empty list ([]) is rejected with 400 validation_error.

payerName is information about the buyer, and you supply this field; the buyer does not enter it themselves. It can be up to 120 characters.

The payment page shows payerName masked: the first two characters of each word stay visible (one character for a single-letter word), the rest are replaced with *, and word length is preserved — for example, MUSTAFA ALI AYDIN is displayed as MU***** AL* AY***. In this API’s create, query, and list responses, payerName comes back in raw form instead, because only you (with your API key) or your merchant panel can reach those responses — you already sent the value yourself. Masking applies only on the buyer’s side, where you share the link’s address (url); anyone who obtains that address should not see the buyer’s full name.

If you put a value that looks like a card number or an IBAN into items[].name, items[].note, or payerName on its own (for example, "4111111111111111"), the request is rejected with 400 sensitive_data_not_allowed. This check only triggers when the entire field matches that pattern; a card number or IBAN embedded inside other text (for example, "Membership fee, card: 4111111111111111") is not rejected. You should not put card or bank account data in these fields anyway; this check is only an extra safety layer.

Code HTTP Reason
rate_limited 429 The API-key or shared source-IP budget was exceeded. Retry after the Retry-After duration.
payment_link_not_found 404 The link was not found or does not belong to this partner.
payment_link_already_paid 409 The link is already completed. (Rarely, the same error is returned if your cancellation races with a payment completing at that exact moment.)
payment_link_expired 409 The link’s window has expired; it can no longer be cancelled.
payment_link_cancelled 409 The link was already cancelled.
payment_link_items_amount_mismatch 422 items was sent, but the amounts do not add up to amount.
installment_not_allowed 422 allowedInstallmentCounts was sent empty, or is not a subset of your terminal’s effective installment set.

Full list: Error Codes.

When a link completes or expires, Lydia Gate sends you a webhook (payment_link.completed / payment_link.expired); the body carries this page’s id field (named paymentLinkId) instead of paymentCode. Detail and a sample body: Webhooks.