Enter at least two characters to search. Searching documentation… No matching sections. Try an endpoint, field or event name.
Payment links
View as MarkdownA payment link is one reusable public checkout page, opened by many customers, who each pick their own coin: a product sold again and again, a tip jar, a link on your site. Create it once and share it. To charge one customer, create a charge instead: do not make a new link per customer or per order.
On this page
Not sure a link is what you need? See which one to use
The payment link object
| Field | Type | Description |
|---|---|---|
id
string
|
string | Public code, also the last path segment of the checkout URL. |
object
"payment_link"
|
"payment_link" | The kind of object. |
title
string
|
string | Shown to the customer. Up to 255 characters. |
description
string|null
|
string|null | Optional, shown under the title. Up to 2,000 characters. |
pricing_type
"fixed" | "open"
|
"fixed" | "open" | Fixed price, or the customer types the amount. |
amount
string|null
|
string|null | Decimal string with two places when fixed (0.01 or more), null when open. |
suggested_amounts
array
|
array | Up to four one-click amounts offered on an open-amount checkout, each within min_amount and max_amount; the customer can still type another. They come back as you sent them, numbers or strings. Always empty for fixed pricing. Creating a link also accepts them as one string, such as "5, 10, 25". |
currency
string
|
string | USD, EUR, GBP, CAD, AUD, CHF, TRY |
status
"active" | "archived"
|
"active" | "archived" | Archived links return 404 to customers. |
success_url
string|null
|
string|null | Once the customer has paid, the checkout shows a button back to it; there is no automatic redirect. An https:// address on a public website, up to 2,048 characters. |
accepted_assets
array|null
|
array|null | The coins the checkout offers, for example ["BTC", "ETH"]: at least one, each a coin the platform takes in this mode. A coin you have no payout address for is accepted here but only offered once you add one. null offers every coin the account can receive, including ones added later. |
min_amount, max_amount
string|null
|
string|null | For open pricing: the least and the most the customer may enter, 0.01 or more, the maximum no lower than the minimum. null for no limit, and always null on fixed pricing. |
cancel_url
string|null
|
string|null | Adds a "cancel and return" link to the checkout. An https:// address on a public website, up to 2,048 characters. |
collect_name, require_email
boolean
|
boolean | Ask for the customer name, and make the email mandatory, on the hosted checkout. |
expires_at, max_payments
string|null, integer|null
|
string|null, integer|null | Close the link after a date or a number of paid charges (1 to 1,000,000). expires_at is ISO 8601 and has to lie in the future; a bare date such as 2026-12-31 means 23:59:59 UTC that day. |
accepting_payments
boolean
|
boolean | Whether the link takes payments right now: active, not past expires_at and short of max_payments. |
views_count
integer
|
integer | How many visitors opened the checkout page, counted once per browser session. |
custom_fields
array
|
array | Up to five extra questions the hosted checkout asks: label (up to 60 characters, no two alike) and required. Questions without a label are dropped. The answers, up to 255 characters each, come back on the charge. |
success_message
string|null
|
string|null | Up to 1,000 characters, shown to the customer after payment and included in the receipt email. |
checkout_url
string
|
string | The hosted page to share. |
created_at, updated_at
string
|
string | ISO 8601 timestamps in UTC. |
collect_name, require_email and the required custom_fields are asked for on the hosted checkout only. A charge you create through the link with POST /v1/charges skips them, and takes the answers you have under customer and custom_fields.
Endpoints
-
GET
/v1/payment-linksList links, newest first, to find one to share again. Filter with ?status= and ?search= (the title). Paginated with ?page= and ?per_page= (max 100). -
POST
/v1/payment-linksCreate a link. Body: title, description, pricing_type, amount, suggested_amounts, currency, accepted_assets, min_amount, max_amount, success_url, cancel_url, collect_name, require_email, custom_fields, success_message, expires_at, max_payments. title, pricing_type and currency are required, and amount too for fixed pricing. -
GET
/v1/payment-links/{id}Retrieve a link. -
PATCH
/v1/payment-links/{id}Update any subset of the same fields. Switching to fixed pricing requires an amount, and switching to open clears it. custom_fields replaces the whole list. Suggested amounts are not checked against min_amount and max_amount here; any outside them are left off the checkout and the response. -
POST
/v1/payment-links/{id}/archiveStop accepting payments. -
DELETE
/v1/payment-links/{id}/archiveRestore an archived link.
Example: create a link
Copy failed. Select the code and copy it manually.
curl -X POST 'https://api.velirapay.com/v1/payment-links' \
--fail-with-body --silent --show-error --max-time 30 \
-H "Authorization: Bearer ${VELIRAPAY_API_KEY:?Set VELIRAPAY_API_KEY}" \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"title": "Consulting hour",
"pricing_type": "fixed",
"amount": "150.00",
"currency": "USD",
"success_url": "https://example.com/thanks"
}'
<?php
use VeliraPay\VeliraPayClient;
require __DIR__.'/vendor/autoload.php';
$apiKey = getenv('VELIRAPAY_API_KEY') ?: throw new RuntimeException('Set VELIRAPAY_API_KEY.');
$velirapay = new VeliraPayClient($apiKey, baseUrl: 'https://api.velirapay.com');
$link = $velirapay->paymentLinks->create([
'title' => 'Consulting hour',
'pricing_type' => 'fixed',
'amount' => '150.00',
'currency' => 'USD',
'success_url' => 'https://example.com/thanks',
]);
echo json_encode($link, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR), PHP_EOL;
use VeliraPay\Laravel\Facades\VeliraPay;
$link = VeliraPay::paymentLinks()->create([
'title' => 'Consulting hour',
'pricing_type' => 'fixed',
'amount' => '150.00',
'currency' => 'USD',
'success_url' => 'https://example.com/thanks',
]);
const secretKey = process.env.VELIRAPAY_API_KEY;
if (!secretKey) throw new Error('Set VELIRAPAY_API_KEY.');
const response = await fetch('https://api.velirapay.com/v1/payment-links', {
method: 'POST',
headers: {
Authorization: `Bearer ${secretKey}`,
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'Consulting hour',
pricing_type: 'fixed',
amount: '150.00',
currency: 'USD',
success_url: 'https://example.com/thanks',
}),
redirect: 'error',
signal: AbortSignal.timeout(30_000),
});
if (!response.ok) {
throw new Error(`VeliraPay ${response.status}: ${await response.text()}`);
}
const link = response.status === 204 ? null : (await response.json()).data;
console.log(JSON.stringify(link, null, 2));
import json
import os
import requests
api_key = os.environ["VELIRAPAY_API_KEY"]
response = requests.post(
"https://api.velirapay.com/v1/payment-links",
headers={
"Authorization": f"Bearer {api_key}",
"Accept": "application/json",
},
json={
"title": "Consulting hour",
"pricing_type": "fixed",
"amount": "150.00",
"currency": "USD",
"success_url": "https://example.com/thanks",
},
timeout=(5, 30),
allow_redirects=False,
)
if not 200 <= response.status_code < 300:
raise RuntimeError(f"VeliraPay {response.status_code}: {response.text}")
link = None if response.status_code == 204 else response.json()["data"]
print(json.dumps(link, indent=2))
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var secretKey = Environment.GetEnvironmentVariable("VELIRAPAY_API_KEY")
?? throw new InvalidOperationException("Set VELIRAPAY_API_KEY.");
using var client = new HttpClient(new HttpClientHandler { AllowAutoRedirect = false })
{
Timeout = TimeSpan.FromSeconds(30),
};
using var request = new HttpRequestMessage(HttpMethod.Post, "https://api.velirapay.com/v1/payment-links");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Content = new StringContent("""
{
"title": "Consulting hour",
"pricing_type": "fixed",
"amount": "150.00",
"currency": "USD",
"success_url": "https://example.com/thanks"
}
""", Encoding.UTF8, "application/json");
using var response = await client.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"VeliraPay {(int)response.StatusCode}: {responseBody}");
}
JsonElement? link = null;
if (response.StatusCode != HttpStatusCode.NoContent)
{
using var document = JsonDocument.Parse(responseBody);
link = document.RootElement.GetProperty("data").Clone();
}
Console.WriteLine(link?.GetRawText() ?? "null");
Copy failed. Select the code and copy it manually.
{
"data": {
"id": "6dprcllogj1a",
"object": "payment_link",
"title": "Consulting hour",
"pricing_type": "fixed",
"amount": "150.00",
"currency": "USD",
"status": "active",
"accepting_payments": true,
"checkout_url": "https://velirapay.com/pay/6dprcllogj1a",
…
}
}
Filling in what you already know
Add email and name to the checkout URL to fill those fields in for a customer who is signed in on your site; they can still change them. The name is only used when the link asks for one, and an email that is not valid is ignored. The values travel in the URL, so they end up in browser history. To charge one customer with their details already on the payment, create a charge instead.
Copy failed. Select the code and copy it manually.
https://velirapay.com/pay/6dprcllogj1a?email=jane%40example.com&name=Jane%20Doe
Add coupon to apply one of your coupons for the customer, for example ?coupon=SPRING10. See coupons