Skip to main content
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

Stripemoneydevkit
Create PaymentIntent, wait for webhookCreate checkout, check status directly
Write webhook handlers for payment eventsOne-time route setup, no event handlers
Handle webhook verification & retriesPayment status available immediately
Complex idempotency handlingSimple 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

Using metadata for Custom Data

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

1

Stripe: Create PaymentIntent

// Server-side
const paymentIntent = await stripe.paymentIntents.create({
  amount: 500,
  currency: 'usd',
  metadata: { orderId: '123' }
})
// Return clientSecret to frontend
2

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
1

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)
}
2

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.)