Checkouts
The Checkout API Resource
Below you'll find all properties for the Vatly Checkout API resource.
Properties
| Name | Type | Description |
|---|---|---|
id | string | Unique identifier for the checkout (starts with checkout_). |
resource | string | The resource type. Always checkout. |
orderId | string | null | Unique identifier for the order created from this checkout. Only available when the checkout has been paid successfully. |
customerId | string | null | The customer associated with this checkout. Always present; null until a customer is associated — for an anonymous checkout this happens when the buyer completes payment. |
testmode | boolean | Whether this checkout is in test mode. |
redirectUrlSuccess | string | The URL to which the checkout should redirect the user after the checkout has been paid successfully. May contain the literal {CHECKOUT_ID} placeholder, which Vatly substitutes with this checkout's ID at creation time (e.g. https://example.com/return?checkout_id={CHECKOUT_ID}). |
redirectUrlCanceled | string | The URL to which the user should get redirected when the user cancels the checkout. Supports the same {CHECKOUT_ID} placeholder as redirectUrlSuccess. |
metadata | object | null | Arbitrary key-value metadata for your application. Up to 50 keys, with key names up to 40 characters and values up to 500 characters. |
locale | string | null | The language the hosted checkout was asked to present in, as sent on creation. null means none was specified and the checkout picks a language from the shopper's browser. One of en, de, fr, nl, es, it, pt, pl, or null. |
status | string | The status of the checkout. Can be created, paid, canceled, failed, or expired. |
expiresAt | string | null | When this checkout will expire (ISO 8601 format). |
createdAt | string | The moment the checkout was created, in ISO 8601 format. |
links | object | HATEOAS links to related resources. Contains checkoutUrl (the hosted checkout page URL), self, and order. order is always present and is null until the checkout completes. |
List all checkouts
GET /v1/checkouts
This endpoint allows you to retrieve a paginated list of all your checkouts. By default, a maximum of ten checkouts are shown per page.
Optional attributes
| Name | Type | Description |
|---|---|---|
limit | integer | The number of checkouts to return (default: 10, max: 100). |
startingAfter | string | A cursor for use in pagination. Returns results after this checkout ID. |
endingBefore | string | A cursor for use in pagination. Returns results before this checkout ID. |
curl -G https://api.vatly.com/v1/checkouts \
-H "Authorization: Bearer live_your_api_key_here" \
-d limit=10
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$checkouts = $vatly->checkouts->page();
{
"data": [
{
"id": "checkout_QdEpFhdSrG4Y3DnfsdqsH",
"resource": "checkout",
"customerId": null,
"orderId": null,
"testmode": false,
"redirectUrlSuccess": "https://example.com/success",
"redirectUrlCanceled": "https://example.com/canceled",
"metadata": {},
"locale": null,
"status": "created",
"expiresAt": "2024-01-16T10:30:00Z",
"createdAt": "2024-01-15T10:30:00Z",
"links": {
"checkoutUrl": {
"href": "https://checkout.vatly.com/checkout_QdEpFhdSrG4Y3DnfsdqsH",
"type": "text/html"
},
"self": {
"href": "https://api.vatly.com/v1/checkouts/checkout_QdEpFhdSrG4Y3DnfsdqsH",
"type": "application/json"
},
"order": null
}
}
],
"count": 1,
"links": {
"self": {
"href": "https://api.vatly.com/v1/checkouts",
"type": "application/json"
},
"next": null,
"prev": null
}
}
Create a checkout
POST /v1/checkouts
This endpoint allows you to start a new hosted Vatly Checkout. Make sure you have at least one subscription plan or one-off product configured in your Vatly account.
Once paid, any subscription plan product assigned to the checkout will kick off a new subscription for that plan.
Required attributes
| Name | Type | Description |
|---|---|---|
products | array | An array of product objects to include in this checkout. Each product can have: id (required, starts with one_off_product_ or subscription_plan_), quantity (optional, default: 1), price (optional, EUR-only price override — see Custom pricing), trialDays (optional, for subscription plans), metadata (optional). |
redirectUrlSuccess | string | The URL to which the checkout should redirect the user after the checkout has been paid successfully. You may include the literal {CHECKOUT_ID} placeholder anywhere in the URL — Vatly substitutes it with this checkout's ID, so your return page can read the checkout ID without a server-side token store. |
redirectUrlCanceled | string | The URL to which the user should get redirected when the user cancels the checkout. Supports the same {CHECKOUT_ID} placeholder. |
Optional attributes
| Name | Type | Description |
|---|---|---|
metadata | object | Arbitrary key-value metadata for your application. |
customerId | string | The ID for an existing customer to associate with this checkout. If provided, the customer's email will be pre-filled. Must match the testmode of the API token. |
billingAddress | object | Optional editable prefill for the billing address fields on the hosted checkout. Accepts the standard address fields (fullName, companyName, taxId, streetAndNumber, streetAdditional, city, region, postalCode, country). When country is supplied it takes precedence over the shopper's IP address for determining their country. A supplied taxId is only a prefill — it is not registered until the buyer submits the checkout. |
locale | string | null | Language to present the hosted checkout in, including its validation and error messages. Send a bare language code (de), a BCP 47 tag (de-AT), or a POSIX / ISO 15897 locale (de_DE) — all three fold to the language, so there are no region-specific variants. The language also carries through to the payment provider's own hosted page. Supported languages: en, de, fr, nl, es, it, pt, pl; anything else is a 422 naming the supported set. Omit it (or send null) to detect the language from the shopper's Accept-Language header, falling back to English. The response echoes back the folded language, not the string you sent. |
curl https://api.vatly.com/v1/checkouts \
-H "Authorization: Bearer live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"products": [
{"id": "one_off_product_Vr8kQdFhSrG4Y3DnfsdqH", "quantity": 1},
{"id": "subscription_plan_Rk5pQrSvWm8NjLhYbUcP", "trialDays": 14}
],
"redirectUrlSuccess": "https://example.com/return?checkout_id={CHECKOUT_ID}",
"redirectUrlCanceled": "https://example.com/canceled",
"locale": "de"
}'
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$checkout = $vatly->checkouts->create([
'products' => [
['id' => 'one_off_product_Vr8kQdFhSrG4Y3DnfsdqH', 'quantity' => 1],
['id' => 'subscription_plan_Rk5pQrSvWm8NjLhYbUcP', 'trialDays' => 14],
],
'redirectUrlSuccess' => 'https://example.com/return?checkout_id={CHECKOUT_ID}',
'redirectUrlCanceled' => 'https://example.com/canceled',
'locale' => 'de',
]);
// Redirect the user to the checkout URL
header('Location: ' . $checkout->links->checkoutUrl->href, true, 303);
{
"id": "checkout_Bm7xNvPwKr3YjTgHcZaE",
"resource": "checkout",
"customerId": null,
"orderId": null,
"testmode": false,
"redirectUrlSuccess": "https://example.com/return?checkout_id=checkout_Bm7xNvPwKr3YjTgHcZaE",
"redirectUrlCanceled": "https://example.com/canceled",
"metadata": {},
"locale": "de",
"status": "created",
"expiresAt": "2024-01-16T10:30:00Z",
"createdAt": "2024-01-15T10:30:00Z",
"links": {
"checkoutUrl": {
"href": "https://checkout.vatly.com/checkout_Bm7xNvPwKr3YjTgHcZaE",
"type": "text/html"
},
"self": {
"href": "https://api.vatly.com/v1/checkouts/checkout_Bm7xNvPwKr3YjTgHcZaE",
"type": "application/json"
},
"order": null
}
}
Custom pricing
Charge an amount that differs from the product's dashboard price by setting price on the item — a Money object whose value is a decimal string with exactly two decimals. Currently only EUR is accepted; any other currency returns a 422. When the customer pays in another currency, the amount is converted from this EUR price at checkout — and for a subscription the converted amount and currency are then fixed, so renewals charge the same. The id still references a product you created in the dashboard; products cannot be created on the fly via the API. Omit price to use the product's configured price.
For a subscription plan, a custom price is not a first-cycle discount — it becomes the subscription's recurring price and carries over to every renewal. This is how you give a cohort a permanent price (for example, a lower price for an early group of customers). If you'd prefer to track that cohort separately, create a dedicated subscription plan at the target price and check out against that instead — the recurring price is the same, but reporting stays cleaner.
curl https://api.vatly.com/v1/checkouts \
-H "Authorization: Bearer live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"products": [
{
"id": "one_off_product_Vr8kQdFhSrG4Y3DnfsdqH",
"quantity": 2,
"price": {"value": "49.99", "currency": "EUR"}
}
],
"redirectUrlSuccess": "https://example.com/success",
"redirectUrlCanceled": "https://example.com/canceled"
}'
$checkout = $vatly->checkouts->create([
'products' => [
[
'id' => 'one_off_product_Vr8kQdFhSrG4Y3DnfsdqH',
'quantity' => 2,
'price' => ['value' => '49.99', 'currency' => 'EUR'],
],
],
'redirectUrlSuccess' => 'https://example.com/success',
'redirectUrlCanceled' => 'https://example.com/canceled',
]);
VAT and reverse charge
As Merchant of Record, Vatly determines the applicable tax when the checkout is paid, based on the buyer's country and tax status — you don't calculate or configure rates yourself. For EU B2B, the buyer's VAT ID is validated against the EU's VIES registry as part of that determination: when a valid VAT ID is supplied and the buyer is in a different country from the supplier, the reverse charge applies (0% VAT) and the resulting order reflects it. Otherwise the buyer's country standard rate applies.
Payment methods in test mode
A few payment methods work in live mode but cannot be completed in test mode, because the payment provider cannot settle them there. A test-mode checkout still lists them, greyed out and labelled as unavailable, so you can see they exist without being able to select one:
| Method | Test mode | Reason |
|---|---|---|
banktransfer | Not selectable | The provider cannot settle bank transfers in test mode. |
paybybank | Not selectable | The provider cannot settle Pay by Bank in test mode. |
Every other method behaves identically in both modes. Test a checkout with any selectable method — creditcard and ideal cover the one-off and mandate flows — and the resulting orders, invoices and webhooks are the same ones live mode produces.
Retrieve a checkout
GET /v1/checkouts/:id
This endpoint allows you to retrieve a checkout by providing the checkout id. Refer to the list at the top of this page to see which properties are included with checkout objects.
curl https://api.vatly.com/v1/checkouts/checkout_QdEpFhdSrG4Y3DnfsdqsH \
-H "Authorization: Bearer live_your_api_key_here"
$vatly = new \Vatly\API\VatlyApiClient();
$vatly->setApiKey('live_your_api_key_here');
$checkout = $vatly->checkouts->get('checkout_QdEpFhdSrG4Y3DnfsdqsH');
{
"id": "checkout_QdEpFhdSrG4Y3DnfsdqsH",
"resource": "checkout",
"customerId": "customer_Hn6pQrSvWm8NjLhYbUcP",
"orderId": "order_Jk4pQrSvWm8NjLhYbUcP",
"testmode": false,
"redirectUrlSuccess": "https://example.com/success",
"redirectUrlCanceled": "https://example.com/canceled",
"metadata": {
"campaign": "summer-sale"
},
"locale": null,
"status": "paid",
"expiresAt": "2024-01-16T10:30:00Z",
"createdAt": "2024-01-15T10:30:00Z",
"links": {
"checkoutUrl": {
"href": "https://checkout.vatly.com/checkout_QdEpFhdSrG4Y3DnfsdqsH",
"type": "text/html"
},
"self": {
"href": "https://api.vatly.com/v1/checkouts/checkout_QdEpFhdSrG4Y3DnfsdqsH",
"type": "application/json"
},
"order": {
"href": "https://api.vatly.com/v1/orders/order_Jk4pQrSvWm8NjLhYbUcP",
"type": "application/json"
}
}
}