Passer au contenu

How to Test a Crypto Payment Gateway Before Going Live

Payora has no sandbox, no testnet mode and no test-mode API keys. Here is the pre-launch checklist that actually works: the live demo checkout, webhooks you sign yourself, and one real $1 payment to your own invoice.

Payora10 min readEN · RU · UK · ES · DE

You cannot test a crypto payment gateway in a sandbox on Payora, because Payora does not have one: no testnet mode, no test-mode API keys, no fake invoices. What you do instead is inspect the real hosted checkout at /demo, replay a webhook you signed yourself against your own handler, then send one real payment of about a dollar to your own invoice on a cheap network. That last step costs pocket change and proves the one thing a sandbox never could.

Why there is no sandbox, and why that is not the problem you think it is

A sandbox is a simulator. It lets you rehearse your code against a fiction the gateway wrote. That is genuinely useful for card processors, where the acquirer, the risk engine and the settlement rails are all opaque and you have no other way in.

Crypto is different. The part that breaks in production is almost never the API call. It is the chain: a payment that lands on the wrong network, a rate lock that expired while the buyer copied the address, a confirmation policy nobody read, an address the server derived from the wrong key. A sandbox mints a paid invoice out of thin air. It tells you nothing about any of that. Worse, it tells you everything is fine, which is the most expensive lie a test can produce.

So the honest way to test a crypto payment gateway is a ladder: everything you can verify for free first, then one small real payment that exercises the whole chain end to end. Here is the order I would run it in.

1. Look at the real checkout before you write a line of code

Open /demo. It mints a live invoice on the real hosted checkout, the same page your buyers will see. No account, no code. Watch what actually happens: how the coin picker behaves, what the rate lock window looks like, what the buyer sees while a transaction is still unconfirmed, what happens when the timer runs out.

Five minutes here saves an argument later, because most integration bugs start life as a wrong mental model of the flow. Read /docs alongside it and match each screen to the invoice fields behind it. Then create your account at /register and get your API key and api_secret.

2. Exercise your webhook handler without spending anything

This is the big one. Your handler is the code that turns "money arrived" into "customer got their thing", and it is the piece you can test exhaustively for free, because you hold the secret that signs the webhook. Anything Payora can sign, you can sign.

The scheme is plain HMAC. The signature is hex(hmac_sha256(timestamp . '.' . rawBody, api_secret)), sent in X-Payora-Signature, with the same timestamp in X-Payora-Timestamp. The timestamp must be within ±120 seconds of now, and every delivery carries an Idempotency-Key header that stays stable across retries of the same event. Sign the raw body bytes, not a re-encoded array — re-encoding is the classic reason a correct signature fails.

<?php
// Sign a webhook exactly the way Payora signs one, and POST it at your own endpoint.
$secret = 'YOUR_API_SECRET';                          // the secret your handler verifies with
$url    = 'https://yoursite.com/payora-webhook.php';  // your endpoint, on your dev host

$body = json_encode([
    'event'      => 'invoice.paid',
    'invoice_id' => 'inv_local_1',
    'order_id'   => '1001',
    'status'     => 'paid',
    'amount'     => '1.00',
    'currency'   => 'USD',
    'asset'      => 'USDT',
    'network'    => 'tron',
], JSON_UNESCAPED_SLASHES);

$ts  = (string) time();
$sig = hash_hmac('sha256', $ts . '.' . $body, $secret);   // lowercase hex

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $body,          // send the RAW body you signed
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'X-Payora-Timestamp: ' . $ts,
        'X-Payora-Signature: ' . $sig,
        'Idempotency-Key: whk_local_1',
    ],
]);
$out  = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
echo "$code\n$out\n";

Point that at your own dev endpoint and you have a webhook generator that costs nothing and runs in a loop. The verification side — constant-time comparison, window checks, why you must read the raw input stream — is covered in detail in webhook signature verification with HMAC-SHA256.

3. The tests that actually catch bugs

Run each of these against your handler and check the database, not just the HTTP status. The expected results are not footnotes; they are the test.

  • Valid signature → 200, order marked paid, exactly once.
  • The same webhook delivered twice (same Idempotency-Key) → still exactly one paid order. No double credit, no second licence key, no second shipping label. This is the single most common real bug in payment integrations, and retries are normal, not exceptional.
  • Tampered body with the old signature — change amount from 1.00 to 1000.00, keep the signature → rejected, order NOT paid.
  • Wrong or absent signature → rejected. An empty X-Payora-Signature must fail exactly like a wrong one.
  • Stale timestamp — set it to ten minutes ago → rejected on the window, even though the HMAC itself is valid.
  • Unknown or foreign invoice id → rejected cleanly. No fatal error, no 500, no half-written row.
  • Your endpoint returns 500 → the retry that follows must not double-apply. Kill the database connection mid-handler if you want to be sure.
  • Constant-time comparison — use hash_equals(), never ==. And never log the api_secret, not at debug level, not truncated, not once.

If all eight pass, your handler is in better shape than most live integrations. Now notice what none of them proved: that money can actually reach you.

4. One small real payment to yourself

Now spend a dollar. Create an invoice through the API or a payment link from /accept-payments, open the checkout, and pay it from your own wallet, to your own invoice, with roughly $1.

Pick a cheap, fast network so this costs cents rather than dollars: TON (Gram), Tron and Solana all confirm in seconds for a fraction of a cent. Don't run this on Bitcoin mainnet unless Bitcoin is specifically what you are shipping — you will wait ten minutes per attempt and pay for the privilege.

This single payment is the only test that exercises the full chain: rate lock, on-chain detection, confirmation to that asset's policy (see how many confirmations are safe), signed webhook, your database. If it works, your integration works. If it doesn't, the failure is real and specific — which is exactly what you want to discover on a quiet Tuesday and not during a launch.

5. Prove you can get the money out

Everyone skips this. It is the step that hurts.

Before you take a single customer's payment, withdraw that dollar back to a wallet you control. Check /balance, run a withdrawal or a transfer, watch it land. If you plan to pay suppliers or affiliates, push one batch through /mass-payouts with two rows and trivial amounts, and confirm the offline signing step works on your actual machine with your actual key material — not in theory, on the machine.

A gateway you can receive into but cannot withdraw from is not a gateway. It is a trap with a nice checkout page. Finding that out with $1 stuck is a story you tell later. Finding it out with $40,000 stuck is a very different afternoon.

6. Verify the derived addresses are actually yours

This one is specific to non-custodial setups and it is non-negotiable. Payora derives receive addresses from public key material only — your xPub or master public key. Private keys never touch the server. That is the whole security model, and it carries one hard requirement: the xPub you pasted must be the one your wallet actually owns.

So verify it independently. Take an address the server derived for an invoice, derive the same index from your xPub yourself — in your wallet software, or with any offline HD-wallet library — and compare the strings character by character. If they match, every payment lands somewhere you can spend from. If they don't, every payment is lost, silently, with a green checkout and a paid order to keep you company. The xPub and HD wallet explainer walks through the derivation.

Do this before going live. Not after the first complaint.

The trade-off, stated honestly

No sandbox means your first real test costs a little money and a little care. That is a genuine cost and I won't pretend otherwise. What you get in exchange is a test that was true. A sandbox would have handed you a green checkmark for the API call and told you nothing about the rate lock, the confirmation policy, the withdrawal path, or whether the addresses were yours — the four things that actually decide whether you get paid.

A dollar, an afternoon and a checklist beat a simulator that agrees with you.

Ready to run it? Create a free account at /register, mint your first invoice from /accept-payments, and work down the list. The whole ladder takes about an hour and roughly a dollar.

Comments

Ready to get started?

Create an account and have your first invoice running in under an hour.

Ce que cette page répond