Skip to main content

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.

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 (fx_test_...). Sign up here 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:
import { readFileSync } from "node:fs";

const API_KEY = "fx_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:
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 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:
const API_KEY = "fx_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:
node check-status.mjs 9c1a2b3d-4e5f-6789-abcd-ef0123456789
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:
import { createServer } from "node:http";
import { createHmac, timingSafeEqual } from "node:crypto";

const SIGNING_SECRET = "whsec_test_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):
node webhook-server.mjs &
ngrok http 4242
Copy the ngrok HTTPS URL and register it as your workspace’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 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:
NumberBehavior
+15005550001Always succeeds on first attempt
+15005550002Returns busy on every attempt
+15005550005Always 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 for every error code and its recommended action. The 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 (fx_live_...), purchase credits, and use a real E.164 number as the destination.

What to do next

  • Webhook signing - replay protection, secret rotation, and verification in other languages.
  • Idempotency keys - how keys are stored and matched, TTL behavior, and retry best practices.
  • Error catalog - every error code, its trigger, and the recommended action.
  • Events schema - all event types and their payload shapes.
  • Sandbox - magic numbers, simulated failures, and sandbox behavior.