HMAC-signed API
Every merchant request carries a public key plus an HMAC-SHA256 signature of the raw body. You build the same signing code you will use in production.
Multi-currency payment gateway sandbox
FauxPay is a payment gateway simulator with a real API, a hosted checkout page, and signed webhooks that retry. Ten currencies, deterministic test scenarios, and zero real money — so your team can finish the integration today instead of waiting on legal, KYC, and a merchant agreement.
FauxPay checkout
Nusantara Coffee Co.
Amount due
Rp1,500.00
IDR · order ORDER-1029
Payment method
Test controls — success / fail / pending
Supported currencies: IDR, INR, MYR, VND, MMK, THB, JPY, USDT, PHP, PKR.
Why FauxPay exists
Most teams can wire up a payment flow in an afternoon. What takes months is everything around it: the legal entity, the KYC review, the merchant agreement, the go-live checklist. FauxPay fills that gap with a gateway that behaves like the real one and costs less than a team lunch.
Everything a gateway has
If your integration works against FauxPay, it works against the gateway you graduate to. That's the whole design goal.
Every merchant request carries a public key plus an HMAC-SHA256 signature of the raw body. You build the same signing code you will use in production.
Redirect to a token-scoped checkout URL. No auth needed, no merchant secrets in the browser, and a simulation banner your testers cannot miss.
Signed payloads POSTed to your endpoint, retried with exponential backoff — 1m, 5m, 30m, 2h — with every attempt logged and resendable.
Amounts are integers in the smallest unit, with precision read from the currency itself. Add another currency with one database row.
Force an outcome when you create the intent — always_fail, delayed_success, expired. Your QA suite stops depending on which button a human clicks.
Inspect intents, transactions, and each webhook attempt down to the response body and latency. Resend a delivery while you debug your handler.
The whole flow
Register and FauxPay issues your first key pair. The secret is shown once and stored encrypted on our side.
POST the amount, currency, and your order id. Sign the raw body with your secret. You get a checkout URL back.
Redirect to that checkout URL. Your tester picks an outcome, or a scenario forces one for automated runs.
We POST a signed event to your endpoint. Verify the signature, then mark the order paid in your own system.
Integration
Two things to implement: signing your outbound requests, and verifying our inbound webhooks. Both are HMAC-SHA256 over the raw body — the same scheme in both directions.
Serialise the payload once and sign those exact bytes — re-encoding a parsed object will change key order and break the signature.
<?php
$payload = json_encode([
'amount' => 150000,
'currency' => 'IDR',
'order_id' => 'ORDER-1029',
'callback_url' => 'https://merchant.test/webhooks/fauxpay',
'return_url' => 'https://merchant.test/orders/1029/thank-you',
], JSON_UNESCAPED_SLASHES);
$response = Http::withHeaders([
'X-Api-Key' => config('services.fauxpay.public_key'),
'X-Signature' => hash_hmac('sha256', $payload, config('services.fauxpay.secret')),
])->withBody($payload, 'application/json')
->post(config('services.fauxpay.url').'/api/v1/payment-intents');
$intent = $response->json('data');
return redirect($intent['checkout_url']);
Compare in constant time, and only trust this — not the browser redirect — when deciding whether an order is paid.
<?php
$rawBody = $request->getContent();
$signature = (string) $request->header('X-FauxPay-Signature');
$expected = hash_hmac('sha256', $rawBody, config('services.fauxpay.secret'));
if (! hash_equals($expected, $signature)) {
abort(403, 'Invalid signature');
}
$event = json_decode($rawBody, true);
if ($event['status'] === 'success') {
// Safe to fulfil: mark the order paid, keyed by $event['order_id'].
}
| Method | Path | What it does |
|---|---|---|
| POST | /api/v1/payment-intents | Create an intent and receive its checkout URL |
| GET | /api/v1/payment-intents/{id} | Read a single intent |
| GET | /api/v1/payment-intents | List intents, filterable by status and order id |
| GET | /api/v1/transactions | List terminal transactions |
| GET | /api/v1/currencies | Supported currencies and their precision |
| GET | /api/v1/health | Public health check — no signature required |
Deterministic testing
Pass a scenario key when you create the intent and the outcome is decided before your test even runs. Same request, same result, every time.
| Scenario | Forced outcome | Delay | Use it for |
|---|---|---|---|
| always_success | success | 0s | Intent resolves as success immediately, no delay. |
| always_fail | failed | 0s | Intent resolves as failed immediately — useful for QA of failure paths. |
| always_pending | pending | 0s | Intent stays pending until it expires or is resolved manually. |
| delayed_success | success | 5s | Success after a 5 second delay — useful for polling tests. |
| expired | expired | 0s | Intent expires without producing a terminal transaction. |
Create your own by inserting a row into the scenarios table — no code changes required.
Pricing
No per-transaction fees, because there are no transactions. You are paying for a sandbox that behaves like the real thing.
Rp10,000 / month
Monthly. Cancel whenever — your integration keeps working until the end of the period you already paid for.
Start free, upgrade laterNo card details required to try it
How billing works
We collect payment outside FauxPay — by transfer or any channel you already use. A tool that exists to avoid handling real money should not be asking for your card number, so it doesn't.
Safety by construction
These are not policy promises — they are properties of the codebase. There is no processor integration to misconfigure, because none exists.
No Stripe, Midtrans, Xendit, or bank API keys anywhere in the codebase. Ever.
FauxPay never asks for a PAN, CVV, or expiry — not even in test fixtures.
Every API response carries mode: simulation. Every page carries a visible banner.
Subscriptions are invoiced separately, so money never enters the application.
Questions
No. FauxPay is a simulator. It has the shape of a gateway — intents, a hosted checkout, signed webhooks, a backoffice — but it is not licensed to process payments and has no connection to any financial network.
Please don't. There is nowhere for them to go, and storing them would be both pointless and dangerous. Use obviously fake values in obviously fake formats.
As a stand-in, yes — that is the intended use. Your customers can walk the full checkout flow while you finalise a real provider. Just make sure the simulation banner stays visible so nobody mistakes it for a real charge.
We retry with exponential backoff at 1m, 5m, 30m, and 2h, then mark the delivery as given up. Every attempt is logged with its response code and latency, and you can replay a delivery from the backoffice.
On your side, no — just an HTTPS endpoint. FauxPay needs its own queue worker running to deliver them, which is already configured for the hosted service.
Yes, and it is a two-step change: insert a row into the currencies table with its symbol and decimal precision, then use it. No code path is currency-specific.
You swap the base URL, the API keys, and the signature secret. Your checkout flow, order state machine, and webhook handler stay exactly as they are — that is the point of building against a gateway-shaped mock.
Create a sandbox account, grab your keys, and send your first payment intent. It takes about as long as reading this page did.