Testing
Build and exercise the whole payment path without moving money. Test mode is a property of the API key you use — not a separate environment you deploy against.
Test keys
In the Flik portal, turn on Test Mode — the switch at the bottom of the sidebar — then open API Keys and choose New key. A key made while Test Mode is on is a test key; made with it off, a live key. That is fixed for the life of the key — you cannot flip it later — and the keys list shows only the keys for whichever mode the switch is in. Mint one key per environment.
You can tell them apart from the credentials themselves. Test keys
carry flik_test_; live keys carry
flik_live_.
# staging
FLIK_CLIENT_ID=flik_test_cid_8Fq2mXbRt7yLpK3nVwZaQ4
FLIK_CLIENT_SECRET=flik_test_sk_3f9c1a7d2e8b4056cc1d9e7a3b5f21c4
# production
FLIK_CLIENT_ID=flik_live_cid_4Rm9tYuIo2pAsD6fGh1JkT
FLIK_CLIENT_SECRET=flik_live_sk_7b2e4c8a1d3f9056aa7c2e4b8d1f6a30
Assert on the prefix at boot
Because the prefix is part of the credential, your application can check it. Do that in whatever loads your configuration, and refuse to start if the key does not match the environment:
const clientId = process.env.FLIK_CLIENT_ID;
const expected =
process.env.NODE_ENV === "production" ? "flik_live_" : "flik_test_";
if (!clientId || !clientId.startsWith(expected)) {
throw new Error(
`FLIK_CLIENT_ID must start with ${expected} in ${process.env.NODE_ENV}`
);
}
import os
app_env = os.environ.get("APP_ENV", "development")
expected = "flik_live_" if app_env == "production" else "flik_test_"
client_id = os.environ["FLIK_CLIENT_ID"]
if not client_id.startswith(expected):
raise RuntimeError(
f"FLIK_CLIENT_ID must start with {expected} in {app_env}"
)
<?php
$appEnv = getenv('APP_ENV') ?: 'development';
$expected = $appEnv === 'production' ? 'flik_live_' : 'flik_test_';
$clientId = getenv('FLIK_CLIENT_ID');
if (!str_starts_with((string) $clientId, $expected)) {
throw new RuntimeException(
"FLIK_CLIENT_ID must start with {$expected} in {$appEnv}"
);
}
The failure this prevents is a live key sitting in a staging config — copied in during an incident, restored from the wrong secret store, pasted into the wrong pipeline variable. Without the check, nothing looks wrong until your integration tests take real money off a real customer. A crash at boot is a cheap way to find out.
Same URL, same endpoints
Both modes use https://app.flik.co.nz and the same
paths. There is no sandbox host to point at and no
/test/ prefix. Going live means swapping the client
ID and secret; nothing else in your code or configuration changes.
The other half of that is worth saying out loud: nothing in a URL
or a log line tells you which mode a call ran in. The key decides,
and only the key. If you need to know after the fact, look at
testMode on the response.
GET /api/checkout-sessions/{id} answers for both.
What tells them apart is testMode on the response,
which is why your integration tests should assert on it. Keep
the two apart in your own systems: never seed live records from
test ones, and never treat a test payment as money.
What test mode does
Test mode is not a stub. Requests hit the same API, run the same
validation and create real rows: a checkout session gets an id, a
transaction id, an expiry and a checkout URL, and it moves through
the same created, pending,
completed, failed and
expired statuses as any other. Anything your code
does with a live response, it can do with a test one.
What does not happen is the money. No bank is contacted, no card
is processed, nothing settles. Every response created by a test
key carries "testMode": true:
{
"id": "cs_7Hk2pQr4TnVxWy9BdLmE",
"status": "completed",
"amount": { "total": 25.00, "currency": "NZD" },
"checkoutMethod": "both",
"transactionType": "open_banking",
"testMode": true,
"foreignTransactionId": "order-5591"
}
Assert on that field in your integration tests. It is the one signal in the response that proves the suite is not pointed at production.
Completing a test payment
Create the session and redirect the customer to
url, exactly as you would in live. The checkout page
looks exactly like the live one, with a Test mode
badge above the amount. On the card rail, enter one of the
test cards below — any name, email,
future expiry and CVC. On the bank rail, pick any bank and press
Pay: there is no consent step, and the payment settles
immediately.
Either way the session behaves as a completed payment from there
on: the status becomes completed,
transactionType records which rail was simulated, the
customer is sent to your redirectUrl, and the webhook
fires. That is the whole flow, end to end, with no money involved.
Test cards
A test session set to card or both
accepts the numbers below. They are recognised by Flik in test
mode only — no real card is involved, nothing reaches a card
network, and no money moves. Any future expiry and any CVC work.
| Number | Brand | Result |
|---|---|---|
4111 1111 1111 1111 |
Visa | Approved |
5555 5555 5555 4444 |
Mastercard | Approved |
3714 496353 98431 |
American Express | Approved |
4000 0000 0000 0002 |
Visa | Declined |
5555 0000 0000 0008 |
Mastercard | Declined |
3700 000000 00002 |
American Express | Declined |
Test the declines. A card that is refused is the one path most
integrations never exercise, and it is the one your customer hits
on a Friday night. A declined test card leaves the session
status at failed, sends nothing to your
webhook — a decline is not a terminal outcome, and the customer
can try again on the same URL — and is exactly what your checkout
has to recover from in live.
What test mode skips
Test mode does not run the eligibility checks a live payment needs. It does not require an approved open banking application, and it does not require a configured settlement account. That is deliberate — it means you can integrate while your account is still being verified, rather than waiting on approval before you write a line of code.
The consequence matters more than the convenience. A request that succeeds with a test key can fail with a live key. Same payload, same endpoint, same code — and a live key returns an eligibility error because something in the portal is not set up yet. A green test suite proves your integration is correct. It does not prove your account is ready to take payments.
Handle those errors explicitly, and read what they mean on Errors before you go live. They are not bugs in your code; they are instructions about your account.
Webhooks in test mode
Webhooks fire in test mode, to the webhookUrl you
set on the session, signed with the same secret and the same
scheme as live. Completing a test payment posts
checkout_session.completed. Letting one expire posts
nothing — an expired session is a status you read, not an event.
Because the endpoint is a per-session field, test deliveries go wherever you point them — there is no shared endpoint to fight over and nothing to put back afterwards. Verification is testable before you go live, and it should be. Run your endpoint behind a tunnel, complete a test payment, and confirm the signature check passes on a real delivery rather than on a fixture you wrote yourself. See Webhooks.
Going live
Work through this before you take a payment from a customer.
-
Swap in a live key
Replace the credentials in your production configuration with a
flik_live_pair. Nothing else changes — same base URL, same endpoints. Confirm the prefix check from above is running in production too, so a test key in live fails loudly rather than quietly taking no money. -
Verify webhook signatures, if you take webhooks
Skip this if you confirm payments by reading them back, which is what Flik recommends — there is no endpoint to attack. If you do run one, it is public: if it grants goods without checking
Flik-Signature, anyone who finds the URL can place free orders. Verify over the raw body, compare in constant time, and reject stale timestamps — Webhooks has working code. -
Handle every error code
Decide what your application does for each code on Errors, including the eligibility ones test mode never showed you. A payment that cannot start should tell the customer something true, not throw a stack trace at them.
-
Check the portal side
Confirm your settlement account is configured and your payment methods are enabled for the rails and currencies you actually sell in. Open banking is
NZDonly; if you charge inAUD, card is the rail those payments will use. -
Run one real payment
Small amount, live key, your own bank account or card. Follow it all the way: the session completes, the webhook arrives and verifies, your order is fulfilled once, and the money reaches your settlement account. This is the only step that tests the parts test mode deliberately skipped.