# NMI Webhooks

## When to Use This Skill

* How do I receive NMI (Network Merchants) webhooks?
* How do I verify the NMI `Webhook-Signature` header?
* Why is my NMI webhook signature verification failing?
* How do I handle `transaction.sale.success`, `transaction.auth.success`, `transaction.refund.success`, or `transaction.void.success` events?
* What is the `t=` value in the NMI signature header — is it a timestamp?

## How NMI Webhooks Work (Read This First)

NMI does not use the [Standard Webhooks](https://www.standardwebhooks.com/)
spec. Each delivery carries a single custom header:

```
Webhook-Signature: t=f3c1e9a2b7d84c15,s=9b7c...e10a

```

Two facts drive everything below:

1. `t` is a NONCE, not a timestamp. It is a random value NMI generates per
  delivery and includes in the signed content. Because it is not a timestamp,
  NMI documents no replay/timestamp tolerance window — do not try to reject
  "old" deliveries by parsing `t` as a Unix time.
2. The signature signs `"<nonce>.<raw_body>"`. You verify by computing
  HMAC-SHA256 over the nonce, a literal `.`, and the raw, unparsed request
  body, keyed with your signing key, hex-encoding it, and comparing (timing
  -safe) to the `s` value. Re-serializing the JSON breaks the HMAC.

```
NMI ──POST body + "Webhook-Signature: t=<nonce>,s=<hex>"──▶ your endpoint
                                                             │  parse t + s
                                                             │  hmac_sha256(key, t + "." + rawBody)
                                                             ▼
                                              timing-safe compare hex == s → 200

```

The payload envelope is `{ "event_id", "event_type", "event_body" }`. The
`event_type` is a dotted lowercase string like `transaction.sale.success`.

## Verification (core)

```javascript
const crypto = require('crypto');

// Header: "Webhook-Signature: t=<nonce>,s=<lowercase-hex-hmac>"
// t is a NONCE (not a timestamp); the signed content is `<nonce>.<rawBody>`.
function verifyNmiWebhook(rawBody, signatureHeader, signingKey) {
  const parts = {};
  for (const seg of String(signatureHeader || '').split(',')) {
    const i = seg.indexOf('=');
    if (i !== -1) parts[seg.slice(0, i).trim()] = seg.slice(i + 1).trim();
  }
  const { t: nonce, s: signature } = parts;
  if (!nonce || !signature || !signingKey) return false;

  const body = Buffer.isBuffer(rawBody) ? rawBody.toString('utf8') : rawBody;
  const expected = crypto
    .createHmac('sha256', signingKey)
    .update(`${nonce}.${body}`)
    .digest('hex');
  try {
    return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
  } catch {
    return false; // length mismatch = invalid
  }
}

```

There is no official NMI SDK, so verification is manual in every language.
Always verify against the raw body — parse JSON only after the signature
checks out.

> For complete handlers with route wiring, event dispatch, and tests, see:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/nmi-webhooks/examples/express/)
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/nmi-webhooks/examples/nextjs/)
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/nmi-webhooks/examples/fastapi/)

## Common Event Types

Event names are dotted lowercase `transaction.<action>.<result>`, where
`action` is one of `sale`, `auth`, `capture`, `void`, `refund`, `credit`, or
`validate`, and `result` is `success`, `failure`, or `unknown`.

| Event | Fires When | Common Use Cases |
| --- | --- | --- |
| `transaction.sale.success` | A sale (auth + capture) is approved | Fulfil order, send receipt |
| `transaction.sale.failure` | A sale is declined | Notify customer, retry/dunning |
| `transaction.auth.success` | An authorization is approved | Reserve funds, hold order |
| `transaction.capture.success` | A prior auth is captured | Mark order paid, fulfil |
| `transaction.void.success` | A transaction is voided before settlement | Release hold, cancel order |
| `transaction.refund.success` | A settled transaction is refunded | Reverse fulfilment, notify |
| `transaction.credit.success` | An unreferenced credit is issued | Payout/adjustment bookkeeping |
| `transaction.validate.success` | A card validation succeeds | Save card on file |

The `.failure` and `.unknown` result variants exist for every action. See
[references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/nmi-webhooks/references/overview.md) for the full matrix and the
`event_body` payload structure.

## Environment Variables

```bash
NMI_SIGNING_KEY=your_webhook_signing_key   # Merchant Control Panel → Settings → Webhooks

```

The signing key is generated in the NMI Merchant Control Panel under
Settings → Webhooks. It is distinct from your gateway API/security key.

## Local Development

```bash
# Start a tunnel (no account needed) — forwards to your local handler
npx hookdeck-cli listen 3000 nmi --path /webhooks/nmi

```

Register the printed public URL as the endpoint URL under Settings → Webhooks
in the Merchant Control Panel, then run a test transaction to see a delivery.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/nmi-webhooks/references/overview.md) - Event types, the `transaction.<action>.<result>` matrix, payload structure
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/nmi-webhooks/references/setup.md) - Merchant Control Panel configuration, getting the signing key
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/nmi-webhooks/references/verification.md) - Webhook-Signature verification in depth and gotchas