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

By the end of this guide your Telnyx Programmable Fax integration runs against the mintfax sandbox: requests go to https://api.mintfax.com/v1, the retry loop on your side is gone, webhook handlers verify Standard Webhooks signatures and consume the smaller mintfax event set, and you have a known-good cutover path back to Telnyx if you need it. This is a dev-execution guide. For the marketing comparison see the Telnyx compare page. SIP-trunking voice topics and Telnyx phone-number rental are out of scope.

Prerequisites

Step 1: Map the endpoints

The base URL changes. Most resource paths stay close to their Telnyx shape. Two operations have no mintfax equivalent because mintfax handles them internally.
Telnyxmintfax
POST https://api.telnyx.com/v2/faxesPOST https://api.mintfax.com/v1/faxes (multipart)
GET /v2/faxesGET /v1/faxes (cursor-paginated)
GET /v2/faxes/{id}GET /v1/faxes/{id}
POST /v2/faxes/{id}/actions/cancelNo equivalent. mintfax runs faxes to a terminal state.
POST /v2/faxes/{id}/actions/refreshNo equivalent. mintfax pushes status via webhooks.
DELETE /v2/faxes/{id}DELETE /v1/faxes/{id} (terminal-state only)
GET /v2/faxes/{id}/downloadGET /v1/faxes/{id}/image
POST /v2/fax_applicationsNo equivalent. mintfax has no connection_id.
The two missing operations are intentional. mintfax retries automatically and pushes status events as state changes, so cancel and refresh are not primitives a client needs. Verify Run curl https://api.mintfax.com/v1/faxes -H "Authorization: Bearer $MINTFAX_KEY" and confirm a 200 with a data array (empty is fine on a fresh sandbox).

Step 2: Swap authentication

Both APIs use Authorization: Bearer .... The header is identical; the key shape changes.
AspectTelnyxmintfax
HeaderAuthorization: Bearer KEYAuthorization: Bearer KEY
Key prefixNone (opaque string)mfx_test_ (sandbox), mfx_live_ (live)
ScopeAccount-levelPer-environment within an account
Rotation latencyUp to 60 minutes to take effectImmediate
The self-identifying prefix is the one behavior change worth noting: an mfx_test_ key sitting in your production config is now greppable.
# Telnyx
curl -X POST https://api.telnyx.com/v2/faxes \
  -H "Authorization: Bearer KEY_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"connection_id":"1234","from":"+13125551234","to":"+12015550100","media_url":"https://example.com/doc.pdf"}'

# mintfax
curl -X POST https://api.mintfax.com/v1/faxes \
  -H "Authorization: Bearer mfx_test_4eC39HqLyjWDarjtT1zdp7dc" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -F "to=+12015550100" \
  -F "file=@document.pdf"
Verify The response is 201 Created with status: "queued" and a fax_-prefixed id.

Step 3: Rewrite the send-fax body

The send body changes shape because mintfax does not use a Telnyx-style fax application or pull-from-URL flow. Drop these fields entirely: connection_id, from, media_url, media_name, from_display_name, quality, t38_enabled, monochrome, black_threshold, store_media, store_preview, preview_format. Replace media_url with a multipart file upload. Replace client_state with the Idempotency-Key header for correlation, plus the tags map if you need echoed metadata on webhooks. Two ceilings to plan for:
  • File size cap drops from 50 MB on Telnyx to 10 MB on mintfax.
  • mintfax assigns the originating number, so any code that loads a Telnyx-owned DID and passes it as from can be deleted.
T.38, G.711, baud rate, ECM, and resolution toggles are not exposed. mintfax does expose csid, page_size, resolution (standard or fine), and optimize_for (text or photo) per fax. See POST /v1/faxes in the OpenAPI spec for the full body. Verify Submit a fax to the sandbox success magic number +15005550001. The response is 201 with status: "queued". Within seconds, GET /v1/faxes/{id} reports status: "delivered".

Step 4: Delete the retry loop

This is the largest single change. Telnyx does not auto-retry, so your integration almost certainly has retry orchestration somewhere: a queue, exponential backoff, a per-error-code policy, dead-letter handling, and idempotency wrapping. On mintfax all of that becomes dead weight. mintfax retries with exponential backoff, configured per fax via the retries field on the send request (default 3, max 10). Each attempt against the destination fires its own fax.sending event, so the count of fax.sending deliveries is your mid-flight progress signal without your needing to drive it. The terminal events arrive once: fax.delivered on success, fax.failed after retries exhaust. This flips the meaning of fax.failed. Counter logic that increments on every Telnyx fax.failed will under-count by retries after migration. Rewrite handlers to treat fax.failed as terminal, not per-attempt.
Node.js
// Before: Telnyx with custom retry orchestration
async function sendWithRetries(fax, attempt = 0) {
  try {
    return await telnyx.faxes.create(fax);
  } catch (e) {
    if (attempt >= 5 || !isRetryable(e)) throw e;
    await sleep(2 ** attempt * 1000);
    return sendWithRetries(fax, attempt + 1);
  }
}

// After: mintfax handles retries internally
const res = await fetch("https://api.mintfax.com/v1/faxes", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MINTFAX_KEY}`,
    "Idempotency-Key": randomUUID(),
  },
  body: form, // includes optional `retries` field, default 3, max 10
});
Verify Send to the transient-failure magic number +15005550004. The fax fails on attempt 1, mintfax fires a second fax.sending for attempt 2, and attempt 2 delivers. Your handler should see one fax.delivered, not a terminal failure.

Step 5: Update webhook signature verification

Telnyx signs with Ed25519 and a per-account public key. mintfax implements the Standard Webhooks specification: HMAC-SHA256 with a per-endpoint shared secret, signed input is {webhook-id}.{webhook-timestamp}.{body}, signature is base64.
AspectTelnyxmintfax
AlgorithmEd25519HMAC-SHA256 (Standard Webhooks)
Signed contenttimestamp, pipe, raw JSON bodywebhook-id, dot, webhook-timestamp, dot, raw body
Signature headertelnyx-signature-ed25519 (base64)webhook-signature (v1,<base64> tokens, space-separated)
Timestamp headertelnyx-timestampwebhook-timestamp
Event id header(none; in payload)webhook-id - stable across retry attempts, ideal for deduplication
Key characterOne public key per accountOne signing secret per webhook endpoint
Replay windowImplementation-defined5 minutes, enforced by the Standard Webhooks libraries on your side
Two operational consequences. The Standard Webhooks reference libraries handle signature comparison, the 5-minute timestamp tolerance, and parsing multi-secret rotation tokens for you - you do not write the verification by hand. Multiple webhook endpoints now mean multiple secrets, so rotation blast radius shrinks but the secret-management surface grows.
Node.js
import express from 'express';
import { Webhook } from 'standardwebhooks';

const app = express();
const wh = new Webhook(process.env.MINTFAX_WEBHOOK_SECRET);

app.post(
  '/webhooks/mintfax',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    try {
      const event = wh.verify(req.body, {
        'webhook-id': req.headers['webhook-id'],
        'webhook-timestamp': req.headers['webhook-timestamp'],
        'webhook-signature': req.headers['webhook-signature'],
      });
      // dedupe on event.id, route on event.type
      res.sendStatus(200);
    } catch {
      res.status(401).send('Invalid signature');
    }
  },
);
The full multi-language reference and rotation guidance lives on the verify webhook signatures page. Verify Send a fax to +15005550001 with a webhook_url pointing at your endpoint. Confirm your handler accepts the request and rejects a tampered copy with 401.

Step 6: Update webhook event handlers

mintfax fires a smaller event set with a flatter envelope.
TelnyxmintfaxNotes
fax.queuedfax.queued1:1 semantically
fax.media.processed(none)Folded into fax.sending’s pages field
fax.sending.startedfax.sendingFires once per delivery attempt; also the mid-flight retry signal
fax.deliveredfax.deliveredTerminal; credit hold captured
fax.failedfax.failedFires once after retries exhaust, not per attempt
(none)balance.lowAccount-level
(none)balance.topupAccount-level
The envelope changes. Telnyx wraps in data: { event_type, id, occurred_at, payload: {...} } plus a sibling meta: { attempt, delivered_to }. mintfax uses { id, type, created, data: { object: {...} } } with the id (evt_-prefixed, also delivered as the webhook-id header) for deduplication and type for event routing. See the full payload shapes in the event catalog. Code that waits for fax.media.processed to know “the PDF is rendered” should switch to watching fax.sending for a non-null data.object.pages field. Code that bumps a “fax has failed” counter on every fax.failed should treat the event as terminal instead. Failure reasons translate too. Telnyx failure_reason strings (user_busy, receiver_no_response, fax_signaling_error, and so on) become normalized codes on data.object.error_code. For example, Telnyx user_busy maps to mintfax line_busy. The full taxonomy is in the error catalog. Verify Replay a previously-handled event id and confirm your handler treats it as a duplicate. Submit a fax that fails permanently (+15005550005) and confirm exactly one fax.failed event arrives.

Step 7: Translate pricing for the rollout review

Telnyx publishes 0.007perpageplusSIPtrunkingminutes(USdomesticoutboundfrom0.007 per page plus SIP trunking minutes (US domestic outbound from 0.005/min). mintfax publishes per-page-only at 0.060/0.060 / 0.048 / $0.036 depending on tier. Worked example: a 3-page fax that takes 60 seconds to transmit.
Cost componentTelnyxmintfax
Per-page3 x 0.007=0.007 = 0.0213 x 0.060=0.060 = 0.180 (top tier)
SIP minutes1 min x 0.005/min=0.005/min = 0.005$0 (included)
Direct API total$0.026$0.180
Sticker price is roughly 7x higher on mintfax. Your migration brief should also include the engineering hours that disappear with the retry loop, dead-letter, and per-error-code dispatch table you maintained for Telnyx, plus the Telnyx phone-number monthly rental that no longer applies. Build those rows for your specific volume.

Verify

End-to-end check, all against the sandbox:
  1. Send to +15005550001. Receive fax.queued, fax.sending, fax.delivered. The fax record reports pages populated.
  2. Send to +15005550004. Receive fax.queued, fax.sending (attempt 1), fax.sending (attempt 2 after the transient failure), then fax.delivered. Confirm your code routes the repeated fax.sending as in-progress, not terminal.
  3. Send to +15005550005. Receive fax.queued, then a fax.sending per attempt (count depends on your retries setting), then exactly one fax.failed with a normalized data.object.error_code.
  4. Send a request without Idempotency-Key. Confirm the response includes X-Idempotency-Key: not provided; recommended and decide whether to add the header to your client.
  5. Replay a previously-delivered webhook and confirm your handler skips it via the webhook-id header (same value as event.id).
If any step fails, the error catalog lists each error code, the HTTP status, and the next action.

What to do next

  • Verify webhook signatures - the full Standard Webhooks receiver recipe with raw-body capture and key rotation.
  • Event types - every event type, payload shape, and delivery behavior.
  • Idempotency keys - retry safety on outbound API calls.
  • Sandbox - the magic-number matrix used in the verify steps above.
  • Errors - error codes, HTTP statuses, and deterministic next actions.
Last modified on May 14, 2026