> ## 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.

# Track whether a fax was delivered

> Check fax status by polling the API or receiving webhook events

You sent a fax. Now you need to know what happened. mintfax gives you two ways to track delivery: poll the API for the current status, or receive webhook events as the status changes. This guide covers both approaches and helps you decide which one fits your integration.

## Fax status lifecycle

Every fax moves through a predictable set of states:

```
queued -> submitted -> in_progress -> delivered
                                   -> failed
```

| Status        | Meaning                                                         | Terminal? |
| ------------- | --------------------------------------------------------------- | --------- |
| `queued`      | Fax accepted and waiting for processing                         | No        |
| `submitted`   | Sent to the carrier for transmission                            | No        |
| `in_progress` | Carrier is actively transmitting pages                          | No        |
| `delivered`   | Carrier confirmed receipt. Credits captured.                    | Yes       |
| `failed`      | All retry attempts exhausted. Credits released (hold refunded). | Yes       |

A fax reaches exactly one terminal state. Once you see `delivered` or `failed`, the status will not change again.

## Approach 1: Poll the API

Polling works well for one-off scripts, debugging, and simple integrations where you want to check status on your own schedule.

Call `GET /fax/{id}` to retrieve the full fax record, including `status`, `error_code`, `error_message`, and timestamps.

<CodeGroup>
  ```bash curl theme={null}
  curl https://api.mintfax.com/v1/faxes/fax_01H7N9WXYZ8VC2QPK5MTRDE3FA \
    -H "Authorization: Bearer mfx_test_abc123..."
  ```

  ```javascript Node.js theme={null}
  const fax = await fetch(
    "https://api.mintfax.com/v1/faxes/fax_01H7N9WXYZ8VC2QPK5MTRDE3FA",
    {
      headers: {
        Authorization: `Bearer ${process.env.MINTFAX_API_KEY}`,
      },
    }
  );

  const data = await fax.json();
  console.log(data.status); // "queued", "submitted", "in_progress", "delivered", or "failed"
  ```
</CodeGroup>

The response includes everything you need to determine what happened:

```json theme={null}
{
  "id": "fax_01H7N9WXYZ8VC2QPK5MTRDE3FA",
  "status": "delivered",
  "to": "+12125551234",
  "pages": 3,
  "retries_remaining": 3,
  "created_at": "2026-05-09T14:22:01Z",
  "submitted_at": "2026-05-09T14:22:03Z",
  "completed_at": "2026-05-09T14:22:08Z"
}
```

If the fax failed, the response includes `error_code` and `error_message`:

```json theme={null}
{
  "id": "fax_01H7N9WXYZ8VC2QPK5MTRDE3FA",
  "status": "failed",
  "to": "+12125551234",
  "error_code": "line_busy",
  "error_message": "Recipient line busy after all retry attempts",
  "retries_remaining": 0,
  "completed_at": "2026-05-09T14:25:30Z"
}
```

### Poll loop example

If you need to wait for a terminal status, poll with a delay between requests. Fax delivery typically takes 30 seconds to a few minutes depending on page count and carrier conditions.

<CodeGroup>
  ```bash curl theme={null}
  # Poll every 5 seconds until the fax reaches a terminal status
  FAX_ID="fax_01H7N9WXYZ8VC2QPK5MTRDE3FA"

  while true; do
    STATUS=$(curl -s https://api.mintfax.com/v1/faxes/$FAX_ID \
      -H "Authorization: Bearer mfx_test_abc123..." \
      | jq -r '.status')

    echo "Status: $STATUS"

    if [ "$STATUS" = "delivered" ] || [ "$STATUS" = "failed" ]; then
      break
    fi

    sleep 5
  done
  ```

  ```javascript Node.js theme={null}
  async function waitForDelivery(faxId, { interval = 5000, maxAttempts = 60 } = {}) {
    for (let i = 0; i < maxAttempts; i++) {
      const res = await fetch(
        `https://api.mintfax.com/v1/faxes/${faxId}`,
        {
          headers: {
            Authorization: `Bearer ${process.env.MINTFAX_API_KEY}`,
          },
        }
      );
      const data = await res.json();

      if (data.status === "delivered" || data.status === "failed") {
        return data;
      }

      await new Promise((resolve) => setTimeout(resolve, interval));
    }

    throw new Error("Polling timed out");
  }

  const result = await waitForDelivery("fax_01H7N9WXYZ8VC2QPK5MTRDE3FA");
  console.log(result.status, result.error_code ?? "no error");
  ```
</CodeGroup>

<Warning>
  Respect [rate limits](/docs/rate-limits). Polling more frequently than once per second will trigger HTTP 429 responses. A 5-second interval is a reasonable default.
</Warning>

## Approach 2: Webhooks

Webhooks push status updates to your server the moment they happen. Use them for production systems that need real-time delivery confirmation without polling overhead.

### Set up a webhook endpoint

Register your endpoint and subscribe to the events you care about:

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.mintfax.com/v1/webhooks \
    -H "Authorization: Bearer mfx_test_abc123..." \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://acme.com/webhooks/mintfax",
      "events": ["fax.delivered", "fax.failed", "fax.sending"]
    }'
  ```

  ```javascript Node.js theme={null}
  const res = await fetch("https://api.mintfax.com/v1/webhooks", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.MINTFAX_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      url: "https://acme.com/webhooks/mintfax",
      events: ["fax.delivered", "fax.failed", "fax.sending"],
    }),
  });

  const webhook = await res.json();
  // Save webhook.signing_secret - it is only shown once
  console.log(webhook.id, webhook.signing_secret);
  ```
</CodeGroup>

### Events for delivery tracking

These are the events relevant to tracking a fax through its lifecycle:

| Event           | Fires when                                                               |
| --------------- | ------------------------------------------------------------------------ |
| `fax.queued`    | Fax accepted and placed in the processing queue                          |
| `fax.sending`   | Delivery attempt against the destination begins. Fires once per attempt. |
| `fax.delivered` | Carrier confirmed delivery. Terminal.                                    |
| `fax.failed`    | All attempts exhausted or a terminal error code returned. Terminal.      |

For most integrations, subscribing to `fax.delivered` and `fax.failed` is enough. Add `fax.sending` if you want per-attempt visibility into transient failures before the final outcome.

### Webhook payload

Every event payload includes a unique `id` for deduplication and `created` for ordering:

```json theme={null}
{
  "id": "evt_01J9XYZQ8KH7B3R0WXAZP8VW6H",
  "type": "fax.delivered",
  "created": 1730000008,
  "data": {
    "object": {
      "id": "fax_01H7N9WXYZ8VC2QPK5MTRDE3FA",
      "status": "delivered",
      "to": "+12125551234",
      "pages": 3,
      "completed_at": "2026-05-09T14:22:08Z"
    }
  }
}
```

Store each event `id` you process and skip duplicates. Events may arrive out of order, so use the `created` timestamp and `status` to determine the latest state.

### Per-fax webhook override

Need delivery updates for a specific fax sent to a different URL? Pass `webhook_url` when you send the fax:

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.mintfax.com/v1/faxes \
    -H "Authorization: Bearer mfx_test_abc123..." \
    -F "to=+61255550150" \
    -F "file=@document.pdf" \
    -F "webhook_url=https://acme.com/webhooks/urgent-fax"
  ```

  ```javascript Node.js theme={null}
  const form = new FormData();
  form.append("to", "+61255550150");
  form.append("file", fs.createReadStream("document.pdf"));
  form.append("webhook_url", "https://acme.com/webhooks/urgent-fax");

  const res = await fetch("https://api.mintfax.com/v1/faxes", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.MINTFAX_API_KEY}`,
    },
    body: form,
  });
  ```
</CodeGroup>

The per-fax URL receives events for that fax only, in addition to any account-level webhook endpoints.

## Retry behavior

mintfax retries failed delivery attempts automatically. By default, each fax gets up to 3 attempts (configurable from 1 to 5 via fax settings).

Here is how retries work:

1. The carrier reports a failure (busy line, no answer, etc.).
2. If the error is classified as retryable and attempts remain, mintfax schedules the next attempt with a backoff delay.
3. When the next attempt starts, mintfax fires `fax.sending` again. Each attempt fires its own `fax.sending`.
4. If every attempt is exhausted or a terminal error code is returned, mintfax fires `fax.failed` and releases the credit hold.

The `retries_remaining` field on the fax record tells you how many attempts are left. When you poll a fax that is mid-retry, you will see a non-terminal status with a decremented `retries_remaining` value:

```json theme={null}
{
  "id": "fax_01H7N9WXYZ8VC2QPK5MTRDE3FA",
  "status": "queued",
  "retries_remaining": 1,
  "error_code": "line_busy",
  "error_message": "Recipient line busy"
}
```

This fax started with 3 retries, has used 2, and is queued for its final attempt. If this attempt also fails, the status moves to `failed`.

## Choosing an approach

| Consideration    | Polling                          | Webhooks                              |
| ---------------- | -------------------------------- | ------------------------------------- |
| Setup complexity | None - just call `GET /fax/{id}` | Requires a public HTTPS endpoint      |
| Latency          | Depends on poll interval         | Near real-time                        |
| Scale            | Fine for low volume              | Better at high volume                 |
| Infrastructure   | No server required               | Needs a reachable server              |
| Best for         | Scripts, debugging, batch checks | Production apps, real-time dashboards |

You can use both. Poll for quick checks during development, and run webhooks in production for real-time updates. They are not mutually exclusive.

## Test in the sandbox

Use sandbox magic numbers to exercise every delivery outcome without sending real faxes:

| Number         | Outcome           |
| -------------- | ----------------- |
| `+15005550001` | Always delivers   |
| `+15005550002` | Always busy       |
| `+15005550005` | Permanent failure |

Send to `+15005550001` with a sandbox key (`mfx_test_...`) to confirm your polling loop or webhook handler processes a `delivered` status. Send to `+15005550005` to confirm your integration handles `failed`. See [Sandbox](/docs/sandbox) for the full magic number matrix.

## What to do next

* [Webhooks](/docs/webhooks) - endpoint setup, delivery behavior, and signature overview.
* [Webhook signing](/docs/webhooks/verify-requests) - verify that payloads came from mintfax with HMAC-SHA256.
* [Events](/docs/webhooks/events/overview) - full list of event types and payload schemas.
* [Sandbox](/docs/sandbox) - magic numbers and simulated failure scenarios.
* [Errors](/docs/errors) - error codes, HTTP statuses, and recommended actions.
