Xendit API Integration

I’ve integrated Xendit API across multiple projects — including e-commerce websites and mobile apps — to enable seamless QRIS and e-wallet payments using their modern /payment_requests
endpoint.
This project showcases:
- Real-time invoice generation
- Native QR code rendering (no redirection)
- Secure webhook handling
- Live order status updates via Firestore
⚙️ Tech Stack
- Next.js (App Router)
- Xendit Payment API
- Firestore (realtime database)
- React + Tailwind UI for smooth modal UX
🔌 Features Covered:
- ✅ QRIS Payments
- ✅ Webhook Handling
- ✅ Payment Status Monitoring
- ✅ Mobile-ready Integration
- ✅ Secure Callback Validation
📄 Payment Request Example
// /api/xendit/create-payment-request/route.ts
const payload = {
reference_id: `cbk_${Date.now()}`,
currency: 'IDR',
amount: body.amount,
payment_method: { type: 'QRIS' },
metadata: { order_id: body.orderId },
}
const res = await fetch('https://api.xendit.co/payment_requests', {
method: 'POST',
headers: {
Authorization: `Basic ${Buffer.from(process.env.XENDIT_API_KEY + ':').toString('base64')}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
🖼️ QR Code Modal (Native UX)
import QRCode from 'react-qr-code'
export default function InvoicePopup({ qrString }: { qrString: string }) {
return (
<div className="flex flex-col items-center p-6">
<QRCode value={qrString} size={200} />
<p className="text-sm text-gray-500 mt-2">Scan with any QRIS-compatible app.</p>
</div>
)
}
🔔 Webhook Listener
// /api/xendit/webhook/route.ts
if (req.headers.get('x-callback-token') !== process.env.XENDIT_CALLBACK_TOKEN) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const event = await req.json()
await db.collection('orders').doc(event.metadata.order_id).update({
status: event.status === 'SUCCEEDED' ? 'paid' : event.status.toLowerCase(),
payment_id: event.id,
updated_at: Date.now(),
})
🔐 Webhook validation is essential for secure integrations.
🔄 Real-Time UI Update
Thanks to Firestore, the frontend listens to order status:
useEffect(() => {
const unsub = onSnapshot(doc(db, 'orders', orderId), (doc) => {
if (doc.data()?.status === 'paid') {
router.push('/checkout/success')
}
})
return () => unsub()
}, [orderId])
📘 Related Article
👉 Integrating Xendit QRIS in a Next.js App Using Webhooks
✅ What This Shows
- I’m familiar with Xendit’s latest APIs (like /payment_requests)
- I can implement full QRIS checkout UX without redirection
- I understand webhook security and validation
- I combine this with Firestore for real-time status
- This works on both web and mobile apps