Guide

Accept a payment

Take a one-time Bitcoin payment end to end: create an invoice, send the customer to the hosted checkout, and confirm settlement.

1. Create an invoice

From your server, create an invoice for the amount you want to charge in USD. Include an Idempotency-Keyso a network retry can't create two invoices for the same order.

const res = await fetch("https://markgroup.app/api/v1/invoices", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MG_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({
    amount_usd: 100,
    description: "Order #1001",
    customer_email: "buyer@example.com",
    expires_in_hours: 24,
  }),
})

const invoice = await res.json()
// Persist invoice.id against your order before redirecting.

2. Send the customer to checkout

The invoice includes a hosted pay_url. Redirect the customer there. It renders the amount due, a QR code, and the on-chain address, and updates automatically as the payment confirms — no checkout UI to build.

return Response.redirect(invoice.pay_url, 303)

Store the id, not just the URL

Save invoice.idalongside your order record. You'll use it to check status and to match the payment later.

3. Confirm the payment

Until webhooks are available, poll the invoice every few seconds while the customer is on the checkout page, and as a safety net with a background job. Fulfil the order when status becomes paid.

async function isPaid(invoiceId) {
  const res = await fetch(`https://markgroup.app/api/v1/invoices/${invoiceId}`, {
    headers: { Authorization: `Bearer ${process.env.MG_API_KEY}` },
  })
  const invoice = await res.json()
  return invoice.status === "paid"
}

Handle expiry

If you set expires_in_hours, an unpaid invoice moves to expired. Treat that as a failed checkout and let the customer start over with a fresh invoice.