Vatly
Guides

Testing

Use test helper endpoints to simulate billing events like subscription renewals in sandbox mode.

Overview

Vatly provides test helper endpoints that let you simulate billing events without waiting for real billing cycles. These endpoints are only available in test mode and require a test_ prefixed API token.

Taxes in test mode

In test mode, Vatly does not run real tax determination. Checkouts and orders created with a test_ token use a fixed, predefined set of tax rates so results are predictable while you build your integration. These rates are simulated for testing only — they are not guaranteed to match real, current statutory rates, and must never be relied upon for pricing decisions, accounting, invoicing, or tax compliance. Switch to a live_ token to get Vatly's real, jurisdiction-accurate tax determination.

How test-mode rates are applied

The buyer's country determines a single fixed standard VAT rate from a built-in lookup. Any country without a specific entry is taxed at a flat 20%. As an example of multi-rate handling, a Canada (CA) buyer also gets an additional 9.975% QST line. Only the standard rate is ever applied — there are no reduced or product-specific rates, and the lookup is not kept in sync with statutory changes.

Tax IDs and reverse charge in test mode

Tax IDs are not checked against VIES or any real registry in test mode. A test tax ID is treated as valid only when it starts with the buyer's uppercase country code — for example DE123456789 is valid for a German buyer, while de123456789 or FR123… for a German buyer is not.

When a valid test tax ID is supplied and the buyer's country differs from the seller's, a 0% reverse charge is applied (B2B cross-border). Otherwise the buyer's country standard rate applies.

Fast-forward subscription renewal

Simulates a subscription renewal cycle, allowing you to test renewal billing flows, lifecycle events, and webhooks without waiting for the actual billing interval. The subscription must be active and have a scheduled next renewal.

Like a live renewal, this runs asynchronously: the request is accepted immediately, the renewal runs from a queue a moment later, and the outcome arrives on your order.paid / order.payment_failed webhooks. The response therefore returns the subscription before the renewal — renewedAt, renewedUntil, and nextRenewalAt have not moved yet.

Request

POST /v1/test-helpers/subscriptions/{subscriptionId}/fast-forward-renewal

ParameterTypeDescription
subscriptionIdstringID of the subscription to renew (starts with subscription_)

The request body is optional. Omit it to advance the billing cycle and leave the renewal payment pending — the endpoint's original behaviour. Provide it to force a specific renewal payment outcome so you can exercise your payment-recovery handling.

FieldTypeDescription
paymentStatusstringOptional. Outcome to force on the renewal payment: paid or failed. Omit to leave the payment pending. failed declines the payment and starts a payment recovery (delivering order.payment_failed to your webhook); paid settles it.
failureReasonstringOptional. Which decline to simulate, valid only with paymentStatus: failed. Soft declines (insufficient_funds, temporary_decline, general_failure) retry over multiple weeks; every other value is a hard decline that asks the customer for a new payment method. Defaults to general_failure. Allowed values: insufficient_funds, temporary_decline, general_failure, invalid_mandate, mandate_canceled, account_closed, card_expired, card_lost_or_stolen, invalid_card_details, authentication_failed, fraud_suspected.

Example

Advance the cycle and leave the renewal payment pending:

curl -X POST https://api.vatly.com/v1/test-helpers/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE/fast-forward-renewal \
  -H "Authorization: Bearer test_your_api_key_here"

Force the renewal payment to fail with a hard decline to test payment recovery:

curl -X POST https://api.vatly.com/v1/test-helpers/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE/fast-forward-renewal \
  -H "Authorization: Bearer test_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "paymentStatus": "failed",
    "failureReason": "card_expired"
  }'

Response

Returns the subscription as it was before the renewal — renewedAt, renewedUntil, and nextRenewalAt still hold their pre-renewal values because the renewal runs from the queue shortly after. Watch your webhooks for the actual outcome.

{
  "id": "subscription_Lp3mNvBxKw7RjTgYcZaE",
  "resource": "subscription",
  "customerId": "customer_Lp3mNvBxKw7RjTgYcZaE",
  "subscriptionPlanId": "subscription_plan_Rk5pQrSvWm8NjLhYbUcP",
  "testmode": true,
  "name": "Pro Monthly",
  "description": "Full access to all Pro features",
  "billingAddress": {
    "fullName": "John Doe",
    "companyName": "Acme Corp",
    "streetAndNumber": "123 Main Street",
    "city": "Berlin",
    "postalCode": "10115",
    "country": "DE"
  },
  "basePrice": {
    "value": "29.00",
    "currency": "EUR"
  },
  "quantity": 1,
  "interval": "month",
  "intervalCount": 1,
  "status": "active",
  "cancellationReason": null,
  "mandate": {
    "method": "card",
    "maskedIdentifier": "4242"
  },
  "startedAt": "2024-01-15T10:30:00Z",
  "endedAt": null,
  "canceledAt": null,
  "renewedAt": null,
  "renewedUntil": null,
  "nextRenewalAt": "2024-02-15T10:30:00Z",
  "trialUntil": null,
  "scheduledUpdate": null,
  "links": {
    "self": {
      "href": "https://api.vatly.com/v1/subscriptions/subscription_Lp3mNvBxKw7RjTgYcZaE",
      "type": "application/json"
    },
    "customer": {
      "href": "https://api.vatly.com/v1/customers/customer_Lp3mNvBxKw7RjTgYcZaE",
      "type": "application/json"
    }
  }
}

Simulate an order payment

When a fast-forward (or its payment recovery) leaves a renewal payment pending, settle or decline it with the order payment simulation helper. It resolves a payment that is already awaiting an outcome without advancing the billing cycle — useful for driving an order.paid on a renewal, or exercising payment recovery with paymentStatus: failed.

POST /v1/test-helpers/orders/{orderId}/simulate-payment

cURL
curl -X POST https://api.vatly.com/v1/test-helpers/orders/order_Hn5xWqVfKm8RjTgYbUcP/simulate-payment \
  -H "Authorization: Bearer test_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{"paymentStatus": "paid"}'

Like fast-forward renewal, this is asynchronous — the response returns the order still awaiting payment, and the outcome arrives on your webhooks. See Simulate an order payment in the API reference for the request body and full response.

Copyright © 2026