If you’re familiar with Stripe, you’ll find moneydevkit’s approach refreshingly simple. The biggest difference: no webhook handlers to write. You set up the /api/mdk route once during initial setup, and that’s it—no webhook event handling code required.
Key Differences
| Stripe | moneydevkit |
|---|
| Create PaymentIntent, wait for webhook | Create checkout, check status directly |
| Write webhook handlers for payment events | One-time route setup, no event handlers |
| Handle webhook verification & retries | Payment status available immediately |
| Complex idempotency handling | Simple client-side state |
No Webhook Handlers to Write
With Stripe, your backend typically relies on webhooks to know when a payment succeeds:
// Stripe: You need a webhook endpoint
app.post('/webhooks/stripe', async (req, res) => {
const event = stripe.webhooks.constructEvent(...)
if (event.type === 'payment_intent.succeeded') {
// Now you know payment succeeded
await fulfillOrder(event.data.object.metadata.orderId)
}
})
With moneydevkit, payment status is available directly on your success page:
// moneydevkit: No webhook needed
'use client'
import { useCheckoutSuccess } from '@moneydevkit/nextjs'
export default function SuccessPage() {
const { isCheckoutPaid, metadata } = useCheckoutSuccess()
if (isCheckoutPaid) {
// Payment confirmed - fulfill the order
return <p>Payment confirmed!</p>
}
return <p>Verifying payment…</p>
}
What About the /api/mdk Route?
The initial setup includes an /api/mdk route:
// app/api/mdk/route.js
export { POST } from '@moneydevkit/nextjs/server/route'
This is a one-time setup—you export the handler and you’re done. Unlike Stripe webhooks, you don’t write any event handling code. The route is internal infrastructure that allows your serverless Lightning node to receive payments. You never need to parse events, verify signatures, or handle retries.
The /api/mdk route may handle additional functionality in the future, but payment confirmation will always be available directly via useCheckoutSuccess.
Linking Checkouts to Your Users
moneydevkit has first-class support for linking checkouts to your authenticated users via customer.externalId.
Using externalId for Authenticated Users
When a user is logged into your app, pass their ID via customer.externalId:
'use client'
import { useCheckout } from '@moneydevkit/nextjs'
export default function PurchasePage({ user }) {
const { createCheckout, isLoading } = useCheckout()
const handlePurchase = async () => {
const result = await createCheckout({
type: 'AMOUNT',
amount: 500,
currency: 'USD',
title: 'Premium Plan',
successUrl: '/checkout/success',
customer: {
externalId: user.id, // Your app's user ID
name: user.name,
email: user.email,
}
})
if (!result.error) {
window.location.href = result.data.checkoutUrl
}
}
return <button onClick={handlePurchase} disabled={isLoading}>Upgrade</button>
}
When externalId is provided:
- The checkout is linked to a customer record in moneydevkit
- If the customer already exists (matched by
externalId), their stored data is used
- Returning customers won’t be asked for information you already have
- All checkouts and orders are linked to the same customer record
For other custom data (like a registration ID, order reference, or plan type), use the metadata field:
const result = await createCheckout({
type: 'AMOUNT',
amount: 500,
currency: 'USD',
title: 'Event Registration',
successUrl: '/checkout/success',
customer: {
externalId: user.id,
},
metadata: {
registrationId: 'reg_xyz789',
plan: 'premium'
}
})
Retrieve it on your success page:
'use client'
import { useCheckoutSuccess } from '@moneydevkit/nextjs'
export default function SuccessPage() {
const { isCheckoutPaid, metadata } = useCheckoutSuccess()
if (isCheckoutPaid) {
const registrationId = metadata?.registrationId
// Use registrationId to update your database, send confirmation, etc.
return <p>Registration {registrationId} confirmed!</p>
}
return <p>Verifying payment…</p>
}
See the Customers documentation for more details.
Quick Comparison: Checkout Flow
Stripe: Create PaymentIntent
// Server-side
const paymentIntent = await stripe.paymentIntents.create({
amount: 500,
currency: 'usd',
metadata: { orderId: '123' }
})
// Return clientSecret to frontend
moneydevkit: Create Checkout
// Client-side
const result = await createCheckout({
type: 'AMOUNT',
amount: 500,
currency: 'USD',
metadata: { orderId: '123' },
successUrl: '/success'
})
window.location.href = result.data.checkoutUrl
Stripe: Write Webhook Handler
// Server-side webhook handler you must write and maintain
if (event.type === 'payment_intent.succeeded') {
await fulfillOrder(event.data.object.metadata.orderId)
}
moneydevkit: Check on Success Page
// No webhook handler needed - check status directly
const { isCheckoutPaid, metadata } = useCheckoutSuccess()
if (isCheckoutPaid) {
// metadata.orderId available here
}
Summary
- No webhook handlers to write - One-time
/api/mdk route setup, then you’re done
- Payment status available directly - Use
useCheckoutSuccess instead of waiting for webhook events
externalId is first-class - Link checkouts to your authenticated users, with automatic data backfill for returning customers
- Use
metadata for custom checkout data (registration IDs, order references, etc.)