Xendit API Integration

xendit.co/api

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