# Invoice Ninja Webhooks

Invoice Ninja sends an HTTP request to your URL when an entity (client, invoice, quote, credit,
payment, vendor, expense, task, project, product, purchase order) is created, updated, sent,
archived, restored or deleted. Webhooks are **not signed**. You authenticate them with a secret
header that you add to the webhook subscription (Headers key/value) and check on your endpoint.

## When to Use This Skill

* How do I receive Invoice Ninja webhooks?
* How do I verify an Invoice Ninja webhook? (There is no signature: use a secret header)
* How do I handle `create_invoice`, `create_payment` or `update_client` webhooks?
* Why did my Invoice Ninja webhook subscription get archived?
* Why does my endpoint return 401 for Invoice Ninja requests?

## Verification (core)

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

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyInvoiceNinjaRequest(
  headers,                                            // Node lower-cases header names
  secret = process.env.INVOICE_NINJA_WEBHOOK_SECRET,  // exact, whole header value
  headerName = process.env.INVOICE_NINJA_WEBHOOK_HEADER || 'Authorization'
) {
  if (!secret) return false;                          // never accept when unconfigured
  const received = headers[headerName.toLowerCase()];
  return Boolean(received) && safeEqual(received, secret);
}

```

* The header **name is yours**: it is whatever key you typed in the webhook's Headers. Read it
  from `INVOICE_NINJA_WEBHOOK_HEADER` (default `Authorization`) and look it up case-insensitively.
* Compare the **whole** value with `INVOICE_NINJA_WEBHOOK_SECRET`, including any `Bearer ` prefix
  you typed. Do not strip or parse the prefix.
* Python: `hmac.compare_digest(received.encode(), secret.encode())` (bytes, never `str`, which
  raises on non-ASCII).
* Missing header or mismatch: `401`. Bad body: `400`. Handled: `200`.
* Read the body raw (`express.raw({ type: '*/*' })`) and check the header before parsing it.

> **A static shared credential authenticates the sender, not the body.** There is no signature
> and no timestamp, so it gives no replay protection and does not prove the body is unmodified.
> Use HTTPS only and rotate the header value in Invoice Ninja (and your env var) if it leaks.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/invoice-ninja-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/invoice-ninja-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/invoice-ninja-webhooks/examples/fastapi/).

## Never Answer 404, 405 or 410

If your endpoint responds `404`, `405` or `410`, Invoice Ninja treats the URL as gone and
**soft-deletes the webhook subscription**, the same state as archiving, so it stops sending until
you restore it. Use `401` for bad credentials and `400` for bad bodies.
Check that your framework or proxy doesn't return one of these for an unexpected method or path.
Other 4xx and 5xx responses are logged and **not retried** (only connection errors and `429` are
retried, up to 5 tries), so a wrong secret loses events instead of delaying them.

## Request Method and Body

* The method is chosen on the subscription (`rest_method`). Invoice Ninja's server only accepts
  `post` (the default) or `put` there, so accept `POST` and `PUT` on the route. Hookdeck's
  Invoice Ninja source pre-selects `POST` without managing the method, so allow `PUT` on the
  source too if you use it.
* The body is JSON: the entity as produced by Invoice Ninja's transformer, with no wrapper and
  **no event field**.

## Routing Events

The event key names the subscription only; it is not sent in the request. Route by URL (one
subscription per event, for example `https://example.com/webhooks/invoice-ninja?event=create_invoice`)
or by entity shape (`line_items` for documents, `applied` for payments, `contacts` plus
`paid_to_date` for clients, because vendors have `contacts` too).
The examples support both.

## Event Keys

Exact keys, as listed in Invoice Ninja's `Webhook` model. Each is `<action>_<entity>`.

| Key | Fires When |
| --- | --- |
| `create_invoice` | An invoice is created |
| `create_payment` | A payment is created |
| `update_client` | A client is updated |
| `sent_invoice`, `late_invoice`, `remind_invoice` | An invoice is sent, goes late, or a reminder is sent |
| `create_quote`, `approve_quote`, `expired_quote` | Quote lifecycle |
| `create_client`, `create_credit`, `create_expense`, `create_task`, `create_project`, `create_vendor`, `create_product`, `create_purchase_order` | Entity created |

The same `update_`, `archive_`, `restore_` and `delete_` actions exist for most entities. See
[references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/invoice-ninja-webhooks/references/overview.md) for the full list.

## Environment Variables

```bash
INVOICE_NINJA_WEBHOOK_HEADER=Authorization        # header key you added to the webhook
INVOICE_NINJA_WEBHOOK_SECRET=your-secret-value    # the exact, whole header value

```

## Local Development

Invoice Ninja can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 invoice-ninja --path /webhooks/invoice-ninja

```

Use the printed URL as the webhook Target URL in Invoice Ninja.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/invoice-ninja-webhooks/references/overview.md) - What Invoice Ninja webhooks are, event keys, payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/invoice-ninja-webhooks/references/setup.md) - Create the webhook and add the secret header
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/invoice-ninja-webhooks/references/verification.md) - Header check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/invoice-ninja-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests