Quick Start
Take your first crypto payment in about fifteen minutes: create an intent, redirect the customer, fulfil on a signed webhook. The money goes straight to your wallet.
What Payaider does
Your customer pays from their own wallet. The funds land directly in your wallet — they never pass through Payaider, and we hold no key that could move them. What we do is watch the chain, match the transfer to your invoice, verify it, and tell you.
That non-custodial design has one consequence you need to know up front: because funds go to your own shared address, the amount is the only discriminator available on-chain. So every concurrently-payable invoice gets a unique amount at high decimal precision. That is why a $100.00 order asks for 100.0431 USDT rather than a round number.
Before you start
- A test secret key (
sk_test_…) from the dashboard. Test mode is a full parallel universe backed by testnets — the same API, separate data. - A verified wallet on at least one network, and a route pointing an asset at it. The sandbox merchant already has three.
- A testnet wallet with some test USDT or USDC, so you can actually pay the invoice you create.
Every example below uses the sandbox at https://api.sandbox.payaider.com. Swap the origin and the key prefix for live traffic; nothing else changes.
Step 1 — create a payment intent
Price the order in fiat. Any ISO currency works; settlement is crypto, which is exactly why going global is cheap here.
curl -X POST https://api.sandbox.payaider.com/v1/payment_intents \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0f2b7c34-9a1e-4d6f-8b02-5c7e1a9d3f44" \
-d '{
"amount": 10000,
"currency": "USD",
"metadata": { "order_id": "1234" },
"success_url": "https://shop.example/thanks"
}'{
"id": "pi_7Fk2Qd9RmTs4Vb",
"object": "payment_intent",
"mode": "test",
"status": "created",
"amount": "10000",
"currency": "USD",
"currency_decimals": 2,
"received_amount": "0",
"metadata": { "order_id": "1234" },
"created": "2026-08-27T10:15:00.000Z",
"expires_at": null,
"checkout_url": "https://checkout.payaider.com/c/pi_7Fk2Qd9RmTs4Vb",
"payment_method": null
}Step 2 — redirect the customer
Send them to checkout_url. Hosted checkout does the hard parts: the asset and network picker with wrong-network warnings, the QR code and the plain-address copy button, the 15-minute countdown, live status, the underpayment "remaining" flow, and the "I have paid" transaction-hash fallback.
// 1. Create the intent server-side. Amounts are INTEGER MINOR UNITS:
// 10000 with currency "USD" is $100.00, not $10,000.
const response = await fetch("https://api.sandbox.payaider.com/v1/payment_intents", {
method: "POST",
headers: {
"Authorization": "Bearer " + process.env.PAYAIDER_SECRET_KEY,
"Content-Type": "application/json",
// A retry without this header can mint two intents for one order, and a customer
// who pays both cannot be refunded by anyone, including us.
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
amount: 10000,
currency: "USD",
metadata: { order_id: order.id },
success_url: "https://shop.example/thanks",
}),
});
const payment = await response.json();
// 2. Redirect the customer. Checkout handles asset choice, the QR, the countdown,
// the "I've paid" fallback and the underpayment flow.
res.redirect(payment.checkout_url);
// 3. Fulfil on the webhook. NEVER on the success_url redirect: a customer can open
// that URL without paying, and a payment can confirm after they close the tab.Step 3 — fulfil on the webhook
Register an endpoint once, store the whsec_… secret it returns (you see it exactly once), and verify every delivery.
curl -X POST https://api.sandbox.payaider.com/v1/webhook_endpoints \
-H "Authorization: Bearer sk_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 7c1e4d6f-8b02-4a19-9f3d-2e5c7a9d3f01" \
-d '{ "url": "https://shop.example/webhooks/payaider", "enabled_events": ["*"] }'import express from "express";
import crypto from "node:crypto";
const app = express();
// express.raw is mandatory: the signature is over the RAW bytes, and
// JSON.stringify(JSON.parse(body)) produces different bytes.
app.post("/webhooks/payaider", express.raw({ type: "application/json" }), (req, res) => {
if (!verifyPayaiderSignature(req.get("Payaider-Signature"), req.body, [process.env.PAYAIDER_WEBHOOK_SECRET])) {
return res.status(400).send("bad signature");
}
const event = JSON.parse(req.body.toString("utf8"));
// Delivery is at-least-once and unordered. Dedupe on event.id before doing any work.
if (alreadyProcessed(event.id)) return res.sendStatus(200);
// confirmed and overpaid both count as paid. detected does NOT.
if (event.type === "payment.confirmed" || event.type === "payment.overpaid") {
fulfillOrder(event.data.metadata.order_id);
}
// ACK fast, then work asynchronously: the delivery timeout is 10 seconds.
res.sendStatus(200);
});The verifyPayaiderSignature function is on the Webhooks page, in Node, PHP and Python. Copy it verbatim — it is executed against the real signer in our test suite, so it cannot silently drift from what we send.
Step 4 — pay it on testnet
- Open the
checkout_urlyou were given. - Pick USDT on TRON (or USDC on Base). The page now shows an address, an exact amount and a countdown.
- Send exactly that amount from your testnet wallet. Do not round it — the amount is how we know which invoice you paid.
- Watch the page:
awaiting_paymentbecomesdetectedwithin seconds, thenconfirming, thenconfirmed. - Your webhook endpoint receives
payment.detected,payment.confirmingandpayment.confirmed, in whatever order the network delivers them.
That is the whole integration. Everything below is detail you can read later.
The five mistakes everyone makes once
| Mistake | What happens | Fix |
|---|---|---|
Fulfilling on payment.detected | You ship against a 0-conf transfer that can still be reorged out. | Fulfil on payment.confirmed or payment.overpaid only. |
Trusting success_url | Anyone can open that URL without paying. | The webhook is the only proof of payment. |
| Re-serializing the webhook body before verifying | The signature never matches, because the bytes changed. | Read the RAW body. In Express, express.raw({ type: "application/json" }). |
Sending amount: 100.00 | Rejected as an invalid amount — or worse, accepted as one cent. | Integer minor units: 10000. |
| Parsing amounts as JSON numbers | An 18-decimal token amount silently rounds in your runtime. | They arrive as strings. Parse with a big-integer or decimal type. |
Where to go next
- Payments — the full lifecycle, every state, and what to do in each.
- Webhooks — signature verification in three languages, the retry schedule, the delivery log.
- Wallets — adding and verifying your receiving addresses, and routing assets to them.
- Errors — every error code, what it means and how to fix it.
- Security — what Payaider can and cannot do with your money, and what we ask of you.