# Testing | Vitix Pay

> Build and verify your Vitix Pay integration with test keys, the hosted sandbox checkout, webhook pings and the livemode flag, without moving real money.

Canonical: https://vitix.net/developers/testing
Markdown: https://vitix.net/developers/testing.md


Documentation menu

Test mode is a complete parallel environment: the same API, the same objects and the same webhooks, backed by a hosted sandbox checkout instead of a payment provider. No card is charged and no money moves.

## Test keys
Keys starting with `vx_test_` operate in test mode. Everything they create (`cs_test_…`, `py_test_…`, `evt_test_…`) carries `livemode: false` and is invisible to live keys, and vice versa. Webhook endpoints are mode-scoped too: register your staging URL with the test key and your production URL with the live key.

|  | Test mode | Live mode |
| --- | --- | --- |
| Key prefix | vx_test_ | vx_live_ |
| Checkout url | Hosted sandbox page on api.vitix.net | Hosted payment page (Polar) |
| Minimum amount | Your account minimum (default $10) | $10.00 |
| success_url | https, or http://localhost | https on an allowed domain |
| Webhook endpoint url | https or http | https only |
| Refunds | Settle instantly | Settle asynchronously (pending → succeeded) |
| Invoices | Not available (invoice_unavailable) | PDF via /v1/payments/:id/invoice |
| fee_usd | Always 0 | Actual processing fee |

## The sandbox checkout page
In test mode, `checkout.url` points to a Vitix-hosted page that looks like a real checkout but has two buttons: **Pay**, which completes the checkout, and **Simulate a declined card**, which shows a decline and leaves the checkout open so you can try again. Completing it emits the same `checkout.completed` and `payment.succeeded` events as production and then redirects to your `success_url`.
bash

```
# 1. Create a test checkout (note the vx_test_ key and the localhost success_url)curl https://api.vitix.net/v1/checkouts \ -H "Authorization: Bearer vx_test_••••••••" \ -H "Content-Type: application/json" \ -d '{ "amount": 2000, "customer": { "id": "user_42" }, "success_url": "http://localhost:3000/billing/success?cs={CHECKOUT_ID}" }'# → "url": "https://vitix.net/pay?cs=cs_test_…" (Vitix-hosted sandbox checkout)# 2. Open that url in a browser and click "Pay" (or "Simulate a declined card")# 3. Your webhook receives checkout.completed + payment.succeeded; the browser stays on Vitix's confirmation page
```

- The email you type on the sandbox page is stored on the payment, exactly as in production.

- Opening the page again after paying shows a "Payment complete" state; paying twice is impossible.

- An expired checkout (`expires_at` passed, or `POST /expire`) renders an expired state and emits `checkout.expired`.

## Testing webhooks

1. Register a test endpoint. For local development put your dev server behind a tunnel first:
bash

```
# Expose a local dev server so Vitix can reach your webhooknpx cloudflared tunnel --url http://localhost:3000# or: ngrok http 3000# Register the public URL as a TEST endpoint (http is allowed only in test mode and only for localhost;# tunnels give you https anyway)curl https://api.vitix.net/v1/webhook_endpoints \ -H "Authorization: Bearer vx_test_••••••••" \ -H "Content-Type: application/json" \ -d '{ "url": "https://quiet-otter.trycloudflare.com/api/vitix/webhook", "description": "local dev" }'
```

2. Ping it. The `ping` event is signed like any other, so a passing ping proves your secret and verification code are correct:
bash

```
curl -X POST https://api.vitix.net/v1/webhook_endpoints/we_…/ping \ -H "Authorization: Bearer vx_test_••••••••"# Your endpoint receives:# webhook-id: evt_test_…# webhook-timestamp: 1791959702# webhook-signature: v1,…# {"id":"evt_test_…","object":"event","livemode":false,"type":"ping","data":{"object":{"object":"ping","endpoint":"we_…","message":"Hello from Vitix Pay"}}}
```

3. Run the sandbox flow above and watch `payment.succeeded` arrive. Inspect every attempt with [GET /v1/events/:id/deliveries](https://vitix.net/developers/api#event-deliveries); it shows the status code and body your server returned.

4. Make your handler return 500 once and observe the retry schedule (1 min, 5 min, …). Then fix it and resend instantly with [POST /v1/events/:id/redeliver](https://vitix.net/developers/api#redeliver-event).

## Testing refunds
bash

```
# Test refunds await Vitix staff approval, then emit refund.succeeded and payment.refundedcurl https://api.vitix.net/v1/refunds \ -H "Authorization: Bearer vx_test_••••••••" \ -H "Content-Type: application/json" \ -d '{ "payment": "py_test_…", "amount_usd": 500 }'
```

## Testing error handling
Errors behave identically in both modes. Useful cases to cover:

| Request | Expected |
| --- | --- |
| amount below your minimum | 400 amount_too_small, param amount |
| success_url on a domain you did not register | 400 url_not_allowed, param success_url |
| a second POST with the same Idempotency-Key and a different body | 400 idempotency_key_reused |
| GET /v1/payments/py_test_nope | 404 resource_missing |
| any request with a revoked key | 401 api_key_invalid |
| 101 requests in 10 seconds | 429 rate_limit_exceeded |

## Checklist before going live

- Swap the key for `vx_live_` in production configuration only.

- Create a live webhook endpoint (https) and store its separate secret.

- Make sure your production `success_url` and `cancel_url` domains are on your account's allowed list ([GET /v1/account](https://vitix.net/developers/api#retrieve-account)).

- Validate amounts against `account.limits` in your UI to avoid avoidable 400s.

- Branch on `livemode` in your webhook handler so a stray test event can never credit a production user.

[← Overview](https://vitix.net/developers)[Node SDK →](https://vitix.net/developers/sdk)
Questions or a missing feature? Email [support@vitix.net](mailto:support@vitix.net)
