> ## Documentation Index
> Fetch the complete documentation index at: https://docs.macropay.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Integrating Webhooks Locally

> Tunnel live Macropay webhook events to localhost and test signature verification before you ship

Webhooks are how Macropay tells your app what just happened — an order paid, a subscription renewed, an agent's usage metered, a dispute opened. The catch during development: Macropay can't reach `http://localhost`. The CLI solves this by opening a tunnel that relays real events from your organization straight to a port on your machine, signed exactly like production.

This walkthrough gets you receiving and verifying events locally in a few minutes.

<Info>
  Macropay is the **Merchant of Record**. Tax-relevant events (orders, refunds, disputes) reflect activity where we've already remitted sales tax/VAT on your behalf — so your handlers stay focused on app logic, not compliance math.
</Info>

## What you'll need

* The Macropay CLI installed and authenticated
* A local server listening on a known port (e.g. `http://localhost:3000`)
* A route in your app ready to accept `POST` requests with a JSON body

## Steps

<Steps>
  <Step title="Install the CLI">
    On macOS, Linux, or WSL:

    ```bash theme={null}
    curl -fsSL https://macropay.ai/install.sh | bash
    ```
  </Step>

  <Step title="Authenticate">
    Link the CLI to your Macropay account. This opens a browser to complete login.

    ```bash theme={null}
    macropay login
    ```
  </Step>

  <Step title="Start the tunnel">
    Point the listener at the local URL your server runs on. The CLI forwards every webhook your organization emits to that address.

    ```bash theme={null}
    macropay listen http://localhost:3000/
    ```

    If your account has more than one organization, you'll be asked to pick which one to stream events for:

    ```bash theme={null}
    ✔ Select Organization …  Acme Robotics

      Connected  Acme Robotics
      Secret     6t3c8ce2247c493a3ade20uea4484d64
      Forwarding http://localhost:3000

      Waiting for events...
    ```
  </Step>

  <Step title="Set the signing secret">
    Copy the `Secret` printed above and add it to your local environment. Your app uses it to verify that each incoming request genuinely came from Macropay.

    ```bash theme={null}
    # .env
    MACROPAY_WEBHOOK_SECRET=6t3c8ce2247c493a3ade20uea4484d64
    ```

    <Warning>
      This secret is unique to the tunnel session. If the verification fails — wrong secret, missing header, or a modified body — Macropay's webhook utilities reject the request with a **403**. A stuck integration almost always traces back to a stale or unset `MACROPAY_WEBHOOK_SECRET`.
    </Warning>
  </Step>

  <Step title="Receive an event">
    Trigger an action in the [dashboard](https://app.macropay.ai) — create a test order, start a subscription, or send an agent signal — and watch it land on your handler. Macropay signs payloads with the [Standard Webhooks](https://www.standardwebhooks.com/) spec, so verification is one call in any SDK.

    <CodeGroup>
      ```ts TypeScript theme={null}
      import { Webhooks } from "@macropay/sdk";

      const webhooks = new Webhooks(process.env.MACROPAY_WEBHOOK_SECRET!);

      // e.g. an Express / Next.js route handler
      const event = webhooks.verify(rawBody, headers);

      switch (event.type) {
        case "order.paid":
          // Tax already collected & remitted by Macropay (MoR)
          grantAccess(event.data.customer_id);
          break;
        case "subscription.canceled":
          revokeAccess(event.data.customer_id);
          break;
      }
      ```

      ```python Python theme={null}
      from macropay import Webhooks

      webhooks = Webhooks(os.environ["MACROPAY_WEBHOOK_SECRET"])

      event = webhooks.verify(raw_body, headers)

      if event.type == "order.paid":
          grant_access(event.data.customer_id)
      elif event.type == "subscription.canceled":
          revoke_access(event.data.customer_id)
      ```
    </CodeGroup>
  </Step>
</Steps>

## How the tunnel works

```
Macropay  ──(signed webhook)──▶  CLI listen  ──(forward)──▶  http://localhost:3000
```

The CLI holds an outbound connection to Macropay and relays each event to the target URL you passed to `listen`. Nothing about the payload or its signature changes in transit — what you receive locally is byte-for-byte what production would deliver, so a handler that passes here passes when you deploy.

<Tip>
  Building **agent or usage-based billing**? Local tunneling is the fastest way to confirm your handler reacts correctly to events like metered usage, value receipts, and signal ingestion before any real spend flows through. Stop the listener with `Ctrl+C` when you're done.
</Tip>

## Ready for production

Once your handlers work locally, register a permanent HTTPS endpoint in the dashboard so Macropay delivers events directly to your deployed server — no tunnel required.
