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

# Node.js quickstart

> Send your first fax with Node.js, check its status, and verify a webhook signature.

## Outcome

You will send a fax to a sandbox magic number, poll for its status, receive a webhook event, and verify the HMAC signature. Everything runs against the sandbox. No real faxes are sent and no credits are charged.

## Prerequisites

* A mintfax sandbox API key (`mfx_test_...`). [Sign up here](https://mintfax.com/register) if you do not have one.
* Node.js 18 or later (for native `fetch` and `crypto`).
* A PDF file to fax. Any single-page PDF works.
* A publicly reachable URL for webhooks, or a tunnel tool such as ngrok.

## Step 1: Send a fax

Post a fax to the sandbox success number `+15005550001`. This number always delivers on the first attempt.

Create `send-fax.mjs`:

```javascript theme={null}
import { readFileSync } from "node:fs";

const API_KEY = "mfx_test_abc123def456";
const BASE_URL = "https://api.mintfax.com/v1";

const file = readFileSync("document.pdf");
const form = new FormData();
form.append("to", "+15005550001");
form.append("file", new Blob([file], { type: "application/pdf" }), "document.pdf");

const response = await fetch(`${BASE_URL}/fax`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: form,
});

const fax = await response.json();
console.log(fax);
// { id: "9c1a2b3d-...", status: "queued", to: "+15005550001", created_at: "..." }
```

Run it:

```bash theme={null}
node send-fax.mjs
```

**Verify:** The response status is `201` and the `status` field is `queued`. Save the returned `id` for the next step.

The `Idempotency-Key` header makes this request safe to retry. If the network drops and you re-run the same script, mintfax returns the original response without sending a duplicate fax. See [Idempotency keys](/docs/idempotency) for details.

## Step 2: Check fax status

Poll `GET /fax/{id}` until the fax reaches a terminal state (`delivered` or `failed`).

Create `check-status.mjs`:

```javascript theme={null}
const API_KEY = "mfx_test_abc123def456";
const BASE_URL = "https://api.mintfax.com/v1";
const FAX_ID = process.argv[2]; // pass the fax ID as a CLI argument

async function poll() {
  for (let i = 0; i < 30; i++) {
    const res = await fetch(`${BASE_URL}/fax/${FAX_ID}`, {
      headers: { Authorization: `Bearer ${API_KEY}` },
    });
    const fax = await res.json();
    console.log(`${fax.status} (attempt ${i + 1})`);

    if (fax.status === "delivered" || fax.status === "failed") {
      console.log(JSON.stringify(fax, null, 2));
      return;
    }
    await new Promise((r) => setTimeout(r, 2000));
  }
  console.log("Timed out waiting for terminal state.");
}

poll();
```

Run it with the fax ID from step 1:

```bash theme={null}
node check-status.mjs fax_8aZqRm4yT3vK7pNxJ2bH9c
```

**Verify:** The sandbox success number delivers within seconds. You should see `delivered` printed to the console.

## Step 3: Receive a webhook event

mintfax sends a `fax.delivered` or `fax.failed` event to your webhook URL when a fax reaches a terminal state. The server below listens for these events and verifies the HMAC signature before trusting the payload.

Create `webhook-server.mjs`:

```javascript theme={null}
import { createServer } from "node:http";
import { createHmac, timingSafeEqual } from "node:crypto";

const SIGNING_SECRET = "whsec_3JzE9rYNm2VbQ8P6KxLf1WdGa4Tc";
const PORT = 4242;

const server = createServer((req, res) => {
  let body = "";
  req.on("data", (chunk) => (body += chunk));
  req.on("end", () => {
    const timestamp = req.headers["mintfax-timestamp"];
    const signature = req.headers["mintfax-signature"];

    // Verify the signature before trusting the payload
    const expected = createHmac("sha256", SIGNING_SECRET)
      .update(`${timestamp}.${body}`)
      .digest("hex");

    const valid = timingSafeEqual(
      Buffer.from(expected),
      Buffer.from(signature)
    );

    if (!valid) {
      console.error("Signature verification failed.");
      res.writeHead(400);
      res.end("Invalid signature");
      return;
    }

    const event = JSON.parse(body);
    console.log(`Received ${event.type} for fax ${event.data.id}`);
    console.log(JSON.stringify(event, null, 2));

    res.writeHead(200);
    res.end("ok");
  });
});

server.listen(PORT, () => console.log(`Listening on port ${PORT}`));
```

Start the server, then expose it with ngrok (or any tunnel):

```bash theme={null}
node webhook-server.mjs &
ngrok http 4242
```

Copy the ngrok HTTPS URL and register it as your environment's webhook endpoint in the mintfax dashboard or via the API. Send another fax (step 1). The event arrives within seconds of delivery.

**Verify:** The console prints `Received fax.delivered for fax <id>` and the signature check passes.

The [Webhook signing](/docs/webhooks/verify-requests) guide covers replay protection, secret rotation, and signature verification in other languages.

## Step 4: Handle errors

Swap the destination number in `send-fax.mjs` to trigger different outcomes:

| Number         | Behavior                         |
| -------------- | -------------------------------- |
| `+15005550001` | Always succeeds on first attempt |
| `+15005550002` | Returns busy on every attempt    |
| `+15005550005` | Always fails permanently         |

The fax record's `error_code` and `error_message` fields tell you what went wrong and whether a retry makes sense. See the [Error catalog](/docs/errors) for every error code and its recommended action. The [Sandbox](/docs/sandbox) guide lists all magic numbers and the scenarios they simulate.

## Verify

You have now:

1. Sent a fax with `POST /fax` and an idempotency key.
2. Polled for status until delivery.
3. Received a webhook event and verified its HMAC signature.
4. Triggered error scenarios with sandbox magic numbers.

Everything above ran in the sandbox. To send real faxes, swap your sandbox key for a live key (`mfx_live_...`), purchase credits, and use a real fax number as the destination.

## What to do next

* [Webhook signing](/docs/webhooks/verify-requests) - replay protection, secret rotation, and verification in other languages.
* [Idempotency keys](/docs/idempotency) - how keys are stored and matched, TTL behavior, and retry best practices.
* [Error catalog](/docs/errors) - every error code, its trigger, and the recommended action.
* [Events schema](/docs/webhooks/events/overview) - all event types and their payload shapes.
* [Sandbox](/docs/sandbox) - magic numbers, simulated failures, and sandbox behavior.
