Skip to main content
This guide wires Macropay into a Laravel app end to end: list your catalog, send buyers to a hosted checkout, confirm the order, and process webhooks reliably with a signed, queue-backed handler. Because Macropay is your merchant of record, you don’t touch raw card data and you don’t file sales tax or VAT. We’re the legal seller on the buyer’s statement, we calculate and remit tax worldwide, and your Laravel app only ever talks to a clean REST API. Card details are tokenized in our PCI DSS Level 1 compliant vault — they never reach your server.
Build against the Sandbox first. Point requests at sandbox-api.macropay.ai, run a full purchase with test cards, and confirm webhooks land before flipping a single value to go live.

What you’ll build

1

Authenticate

Store an organization access token as an environment variable.
2

List products

Fetch your catalog from the API and render it in a Blade view.
3

Create a checkout

Open a hosted checkout session and redirect the buyer to pay.
4

Confirm + verify

Read the checkout back on your confirmation page, then trust the webhook.
5

Handle webhooks

Verify signatures, queue the payload, and react to order and subscription events.

1. Set the API key

Macropay authenticates with an organization access token. Create one under your organization settings in the dashboard, then expose it to Laravel:
Terminal
You’ll add the webhook secret to this same file in a later step.

2. List your products

In Macropay, everything you sell — a one-time license, a monthly plan, a usage-metered API — is a product with one or more prices. Fetch them with a single GET. Register the route:
Then create the controller. Swap sandbox-api.macropay.ai for api.macropay.ai when you go live:
Render each product with a buy link. The link carries the price ID — that’s what determines what the buyer is charged at checkout:
The /checkout route is next.

3. Open a checkout session

This route creates a hosted checkout session and redirects the buyer to the Macropay-hosted payment page. After payment, Macropay sends them to your confirmation URL.
Macropay substitutes the literal {CHECKOUT_ID} token in your success_url with the real checkout ID on redirect — so your confirmation page knows which session to read back:
Any link to /checkout?priceId={priceId} — like the buy buttons from step 2 — now opens a fully hosted, localized, PCI-handled checkout. Tax is computed automatically based on the buyer’s location.

4. Confirm the purchase

On redirect, read the checkout back so you can show an order summary:
A redirect is not proof of payment. The checkout starts as confirmed and only becomes a fulfilled sale once you receive a checkout.updated webhook with status succeeded. Grant access from the webhook, never from the confirmation page alone.

5. Handle webhooks

Webhooks keep your database in sync with what’s happening inside Macropay — orders, subscriptions, refunds, and more. This is the source of truth for fulfillment. Add an endpoint from your organization’s settings page using Add Endpoint.

Expose localhost during development

Macropay needs a public HTTPS URL to deliver events. In development, a tunnel such as ngrok or Cloudflare Tunnel forwards a public URL to your local server:
Terminal

Register the endpoint

1

Set the URL

Point the endpoint at your-app.com/api/webhook/macropay. With a tunnel it looks like https://<your-tunnel-id>.ngrok-free.app/api/webhook/macropay.
2

Pick events

Subscribe to the events you care about — see the full list in the webhook events reference.
3

Generate a secret

Create a signing secret. Every delivery is signed with it so you can prove the request really came from Macropay.
4

Store the secret

Add it to your environment alongside the API key.
Terminal

Install the packages

Macropay signs deliveries using the Standard Webhooks spec. Install the verification library plus Spatie’s webhook client, which checks the signature and queues each payload:
Terminal

Wire up the route

Make sure routes/api.php is registered in your application bootstrap:

Configure the client

Publish the config and point it at your signature validator and processing job:
Terminal
Edit the generated config/webhook-client.php so the secret, signature header, validator, and job all line up with Macropay:

Prepare the database and queue

Webhook calls are persisted before processing, so publish and run the migration:
Terminal
Process events off the request cycle with a queue. Set QUEUE_CONNECTION=database in .env (or use Redis), then create and migrate the jobs table:
Terminal

Verify the signature

Create an app/Handler folder with two classes. The first rejects any request that isn’t genuinely from Macropay:

Process the event

The second class extends ProcessWebhookJob and branches on the event type. Fulfillment, access grants, and subscription state all live here:
Run the worker so queued jobs get processed:
Terminal
Your app is now receiving and verifying live webhook events.

Subscription lifecycle, done right

Grant on subscription.active, revoke on subscription.revoked. When a buyer cancels you’ll get subscription.canceled — but they’ve usually paid through the end of the period. Don’t pull access immediately; wait for subscription.revoked (or the period end) so paying customers keep what they bought.

Going live

When you’re ready for real payments: Once you flip those, Macropay handles tax, fraud, chargebacks, and payouts (free ACH and SEPA) as merchant of record — so your Laravel app stays focused on the product, not the paperwork.

Beyond checkout

The same API powers more than one-time purchases:

Usage-based billing

Meter ingested events and bill on consumption — perfect for API products and per-request pricing.

AI & agent billing

Bill AI features by usage, activity, or outcome, and attribute revenue and cost per agent.

Real-time confirmation (optional)

Building a reactive UI? On the confirmation page, listen for the checkout.updated event and update the view the moment its status flips to succeeded, instead of relying on a page refresh. Stuck or want to compare notes? Join our Discord — we’re happy to help.