Testing
Test mode runs the whole flow, from order to webhook to refund, without moving money. Use it for every scenario your code has to handle.
Die Entwicklerdokumentation ist auf Englisch verfügbar.
Test mode
- Use a key that starts with
vp_sk_test_. Everything created with it is a test object (livemode: false) and stays separate from live data. - Test mode works before your business is verified and offers every payment method, whatever is switched on for live mode.
- The same validation applies as in live mode: amount limits per method, currencies, references unique per mode, idempotency.
- Webhook endpoints are per mode. Add a test-mode endpoint to receive test events; see webhooks for testing through a tunnel.
- Nothing is ever sent on a chain, to Cash App or to a card provider.
Simulate a payment
Open the pay_link of a test order and choose a method. The checkout shows a Test controls box:
| Button | Methods | Result |
|---|---|---|
| Simulate payment | crypto, Cash App, card | The order becomes paid; you receive order.paid. |
| Simulate half payment | crypto | Half the amount arrives. The order becomes underpaid with amount_paid at half; you receive order.underpaid. |
| Simulate expiry | any, while nothing is paid | The order ends as if its time ran out; you receive order.expired, the same event the expiry job sends in live mode. |
A crypto test payment shows a fake transaction hash starting with test_. After the simulation the payer is offered the way back to your success_url, so you can test your return page too.
Simulate the other outcomes
| Scenario | How | Event |
|---|---|---|
| Payer never pays | Click Simulate expiry, or create the order with "expires_in": 900 (the minimum, 15 minutes) and leave it. | order.expired |
| You cancel the order | POST /api/v1/orders/:id/void while it is unpaid. | order.voided |
| Payer cancels | Click the cancel link on the checkout. | None. The payer lands on cancel_url with velpay_status=canceled; the order stays open until it expires. |
| Refund | POST /api/v1/orders/:id/refunds on a paid test order, with any well-formed address as destination. | refund.created, then refund.sent right away (no review in test mode) |
| Method not available | An amount below the method's minimum, e.g. "methods": ["card"] with "amount": 500 USD. | None; the API answers 400 amount_too_small (see errors) |
| Retried request | Send the same POST /orders twice with one Idempotency-Key. | One order; the second answer has Idempotent-Replay: 1 |
Checklist before going live
- A paid order is fulfilled exactly once, even when the same event is delivered twice.
- Your code rejects a webhook with a wrong signature (change one byte of the body) and one older than five minutes.
- An underpaid order is not fulfilled automatically.
- An expired or voided order releases stock or reservations.
- Your return page doesn't mark anything paid by itself; it shows "payment being confirmed" until the webhook arrives.
- An API error (for example a key typo) shows the customer a clear message and gets logged with the
Request-Id. - You have added the live webhook endpoint and swapped in the live key. See going live.