<!--
Sitemap:
- [Newton Agent NeoBank](/index)
- [Getting Started](/getting-started)
- [Onboard an Agent](/guides/onboard-an-agent)
- [Give an Agent a Budget](/guides/agent-budget)
- [Pay for an API](/guides/pay-for-an-api)
- [Issue a Card](/guides/issue-a-card)
- [Meter Your API](/guides/meter-your-api)
- [Handle Approvals](/guides/handle-approvals)
- [Listen for Events](/guides/listen-for-events)
- [SDK Essentials](/reference/sdk)
- [ModularVault](/reference/sdk/modular-vault)
- [PlatformClient](/reference/sdk/platform-client)
- [FacilitatorClient](/reference/sdk/facilitator-client)
- [createX402Fetch](/reference/sdk/create-x402-fetch)
- [buildPolicyParams](/reference/sdk/build-policy-params)
- [Networks & Addresses](/reference/networks)
-->

# Listen for Events

Get a signed HTTP callback when something happens on a vault — a payment settles, gets denied, or queues for approval. Register an endpoint, verify each delivery, and act on it.

## How it works

1. You register an HTTPS endpoint for a vault and get back a signing secret.
2. When an event fires, we POST the event to your endpoint, signed with that secret.
3. Your endpoint verifies the signature over the raw body, checks the delivery is fresh, then acts.

Events you'll see include `intent.executed` (a payment settled), `intent.denied`, `intent.pending_approval`, and `intent.expired`. Each delivery is a JSON body tagged with a `type` field; amounts are decimal strings.

## Register an endpoint

Register your endpoint against the vault whose events you want. The response includes the signing secret — store it, it's shown once and never returned again:

```bash
curl -X POST "$PLATFORM_API_URL/webhooks" \
  -H "Authorization: Bearer $OPERATOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "vault_address": "0xYourVault",
    "url": "https://your-app.example.com/webhooks/newton"
  }'
# → { "id": "...", "url": "...", "active": true, "secret": "whsec_..." }
```

List your endpoints with `GET /webhooks?vault=0xYourVault` (the `vault` or `operator` scope is required), remove one with `DELETE /webhooks/{id}`, and roll the secret with `POST /webhooks/{id}/rotate-secret`.

## Verify a delivery

Every delivery carries two headers:

* `X-Newton-Signature` — `hex(HMAC-SHA256(secret, "{timestamp}.{rawBody}"))`
* `X-Newton-Timestamp` — when we sent it, in unix seconds

The timestamp is part of what's signed, so a captured delivery can't be replayed indefinitely. Verify with `verifyWebhookSignature`, which returns a boolean and never throws:

```typescript
import express from "express";
import { verifyWebhookSignature } from "@newton-xyz/isaac";

const app = express();

// Capture the RAW body — verification is over the exact bytes we sent.
app.post(
  "/webhooks/newton",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const ok = verifyWebhookSignature({
      secret: process.env.WEBHOOK_SECRET!, // the whsec_... from registration
      rawBody: req.body, // a Buffer of the exact request bytes
      signature: req.header("X-Newton-Signature")!,
      timestamp: req.header("X-Newton-Timestamp")!,
      // toleranceSecs defaults to 300 — the freshness bound (rejects deliveries older than this)
    });

    if (!ok) return res.sendStatus(401);

    const event = JSON.parse(req.body.toString("utf8"));

    // Deliveries are at-least-once — failures are retried and you can redeliver
    // by hand — so key irreversible side effects on a stable id from the payload
    // (e.g. task_id on intent.executed/intent.denied) and skip any you've handled.
    switch (event.type) {
      case "intent.executed":
        // a payment settled — fulfill the order, update your ledger (once per
        // event.task_id), ...
        break;
      case "intent.pending_approval":
        // a payment is waiting on you — see the approvals guide
        break;
    }

    res.sendStatus(200);
  },
);
```

The one rule that trips people up: pass the **raw** request bytes, not a parsed body. Body parsers re-serialize the JSON — different key order, whitespace, or unicode escapes — which changes the bytes and fails the signature. Capture the raw body (`express.raw()`, or Next.js `await req.text()`) and verify against that, then parse.

`verifyWebhookSignature` also accepts an array of secrets, so during a secret rotation you can hold the old and new secret at once and a delivery signed by either still verifies.

## Note: a different webhook scheme

This signing scheme is for platform deliveries. The Newton gateway has its own, separate webhook notification scheme for protocol-level failures — it uses a `sha256=`-prefixed signature and no timestamp, so don't reuse `verifyWebhookSignature` for those.

## Next

* **[Handle Approvals](/guides/handle-approvals)** — approve or reject the payments that fire `intent.pending_approval`.
* **[Meter Your API](/guides/meter-your-api)** — charge agents to call your API, and get an `intent.executed` per payment.
