What Manual Mode is
Manual Mode is for payments where money moves outside a PSP API — bank transfer, QR, or a personal wallet. SkyPay shows your account details at checkout, the customer pays you, then claims “I’ve paid.” You review and mark the payment complete.
Best for freelancers, social sellers, students, and early launches before merchant API keys.
How the flow works
Manual payments are asynchronous. Confirming a transfer can take minutes or longer — design fulfillment around that delay.
- 1
Customer opens checkout
Status: pending
- 2
Pays via QR or bank transfer
Money goes to your account
- 3
Taps “I’ve made the payment”
Status: waiting
- 4
You confirm in the dashboard
Status: complete
- 5
Your server fulfills the order
Via webhook or verify API
Confirm can take minutes or hours — design for that delay.
Status path
| Status | Who sets it | What it means |
|---|---|---|
pending | SkyPay | Checkout open; customer hasn’t finished. |
waiting | Customer | They tapped “I’ve made the payment.” Awaiting your review. |
complete | You | You confirmed funds. Safe to fulfill after verify/webhook. |
cancelled / invalid | You or customer | Rejected or abandoned — do not fulfill. |
Set up Manual Mode
- 1 Sign up and open the merchant dashboard
- 2 Go to Payment Providers and add a Manual provider (bank / QR / eSewa personal, etc.)
- 3 Enter the account details or QR image customers should pay to
- 4 Choose After mark paid: wait (default) stays on verifying until Complete; continue auto-redirects to success_url after Mark as Paid
- 5 Generate a checkout / payment link as usual (same ck token flow)
- 6 Add a webhook endpoint and enable payment.waiting + payment.completed
Send customers to checkout
Same payment link pattern as the main guide. Generate a signed ck token from
the dashboard, include amount, unique code, and return URLs.
<a href="https://app.skypay.dev/checkout?ck=YOUR_CHECKOUT_TOKEN&amount=500&code=ORDER_8821&success_url=https://yourapp.com/orders/ORDER_8821/paid&failure_url=https://yourapp.com/orders/ORDER_8821/failed">
Pay with SkyPay
</a>Confirm waiting payments
When a customer marks paid, the payment appears under waiting on your dashboard.
- 1 Open Waiting payments on your dashboard
- 2 Match the amount / reference / screenshot against your bank or wallet
- 3 Mark Complete, Cancelled, or Invalid
- 4 On Complete, SkyPay sets status to complete and fires payment.completed
You
Dashboard → Complete
Payment moves from waiting → complete
Webhook: payment.completed
SkyPay POSTs to your HTTPS endpoint. Use this to fulfill — even if the customer left.
Redirect to success_url
Only if checkout is still open and polling. Best-effort UX — not fulfillment.
Redirects vs webhooks
This is the part that trips people up. success_url is a browser return URL. SkyPay does not server-call it when
you click Complete.
Browser redirect
success_url / failure_url
- For the customer’s browser
- Needs checkout/SDK still open
- Can be skipped if they close the tab
- Never trust alone for fulfillment
Server webhook
payment.completed
- Hits your backend over HTTPS
- Fires when you confirm — tab or not
- Signed with SkyPay-Signature
- Then verify → fulfill the order
If checkout is still open
Polling sees complete and redirects the customer
to success_url.
If they already left (common)
No redirect. Your backend still gets payment.completed — fulfill from that.
Fulfill with webhooks
Configure an HTTPS endpoint in the dashboard (Webhooks). Enable at least payment.waiting and payment.completed.
| Event | When it fires | What to do |
|---|---|---|
payment.waiting | Customer marks paid | Alert ops / start review — don’t fulfill yet |
payment.completed | You mark Complete | Verify, then fulfill |
payment.failed / cancelled | Invalid or cancelled | Release hold / notify customer |
Example handler
// Express example — POST /webhooks/skypay
app.post('/webhooks/skypay', express.raw({ type: 'application/json' }), (req, res) => {
const body = req.body.toString('utf8');
// Verify SkyPay-Signature: t=...,v1=hmac_sha256(secret, `${t}.${body}`)
const event = JSON.parse(body);
if (event.type === 'payment.completed') {
const payment = event.data.object; // { code, status, amount, ... }
// Prefer: re-verify, then fulfill
// GET https://app.skypay.dev/api/v1/checkout/payments-verify/${payment.code}
}
res.status(200).send('ok'); // respond fast (< ~5s)
});if (event.type === 'payment.waiting') {
// Optional: notify ops ("new bank transfer claim")
// Do NOT fulfill yet
}Signatures
Each delivery includes SkyPay-Signature: t=timestamp,v1=hmac_sha256(secret, "{t}.{body}").
Respond with 2xx quickly; failed deliveries retry with backoff.
Always verify
After a webhook (or after a redirect), call verify with your API key before fulfillment. Same endpoint for every rail — including Manual.
const res = await fetch(
'https://app.skypay.dev/api/v1/checkout/payments-verify/ORDER_8821',
{ headers: { Key: 'YOUR_API_KEY' } }
);
const { data } = await res.json();
if (data?.status === 'complete') {
// Safe to fulfill / unlock access
}| Element | Value |
|---|---|
| Method | GET |
| Endpoint | https://app.skypay.dev/api/v1/checkout/payments-verify/{code} |
| Header | Key: YOUR_API_KEY |
Production checklist
- Manual provider configured with correct account / QR
- Unique
codeper order - Webhook endpoint live for
payment.completed(and preferablywaiting) - Signature verification on your server
- Fulfill only when verify returns
complete - Ops process to clear waiting payments promptly
- Customer copy: “Stay on this page for instant confirmation, or we’ll email / unlock after we verify your transfer.”
FAQ
Why didn’t my customer get redirected after I confirmed?
Redirects only happen if checkout (or the Flutter SDK) is still open and polling. Manual confirm often happens later — use webhooks or payments-verify to fulfill.
Should I fulfill when status is waiting?
No. waiting means the customer claimed they paid. Fulfill only after you confirm (complete) and your server sees payment.completed or verify returns complete.
Do I need webhooks for Manual Mode?
Strongly recommended. Without them you must poll payments-verify yourself. Webhooks notify you the moment you (or your ops team) mark the payment complete.
Can customers leave before I confirm the transfer?
Yes. Set After mark paid to continue on your Manual provider: after the confirm dialog and Mark as Paid succeeds, checkout auto-redirects to success_url (status still waiting). Fulfill from payment.completed (+ verify) — see the after-mark-paid guide.
Skip the verifying wait when ops is slow
Use Continue after mark paid for immediate success_url redirect — fulfill only on payment.completed.
Next steps
Wire webhooks, then try a test Manual payment end-to-end.