Callbacks

When operations complete (send fax, receive fax, direct message, email, AI document), Medsender POSTs to your callback_url. Use this to update your system.

Headers

  • X-Medsender-Signature: HMAC-SHA256 signature for verifying authenticity

Payload Format

Callbacks are sent as form data with a JSON string in a named field. Parse the form field, then parse the JSON inside.

  • Sent Fax: recordDetails field with JSON string (keys in camelCase)
  • Received Fax: recordDetails field with JSON string + file field with PDF attachment (keys in camelCase)
  • AI Document: AiDocumentDetails field with JSON string (nested keys in snake_case)

Retry

If your endpoint returns a non-2xx response, we retry with exponential backoff for a short period.

Webhook Handler Example

Parse the form-urlencoded body and extract the JSON from the appropriate field.

Node
import express from 'express';
import multer from 'multer'; // For handling multipart/form-data (received fax)
const app = express();
const upload = multer({ dest: 'uploads/' });
app.use(express.urlencoded({ extended: true }));

// Sent fax callback (form-urlencoded)
app.post('/webhook/sent-fax', (req, res) => {
  const data = JSON.parse(req.body.recordDetails);
  console.log('Fax status:', data.sentStatus);
  console.log('Send token:', data.sendToken);
  res.status(200).send('OK');
});

// Received fax callback (multipart with PDF file)
app.post('/webhook/received-fax', upload.single('file'), (req, res) => {
  const data = JSON.parse(req.body.recordDetails);
  console.log('From:', data.fromNumber);
  console.log('PDF saved to:', req.file?.path);
  res.status(200).send('OK');
});

// AI document callback (form-urlencoded)
app.post('/webhook/ai', (req, res) => {
  const data = JSON.parse(req.body.AiDocumentDetails);
  console.log('Classification:', data.aiResult.document_classification);
  res.status(200).send('OK');
});
import express from 'express';
import multer from 'multer'; // For handling multipart/form-data (received fax)
const app = express();
const upload = multer({ dest: 'uploads/' });
app.use(express.urlencoded({ extended: true }));

// Sent fax callback (form-urlencoded)
app.post('/webhook/sent-fax', (req, res) => {
  const data = JSON.parse(req.body.recordDetails);
  console.log('Fax status:', data.sentStatus);
  console.log('Send token:', data.sendToken);
  res.status(200).send('OK');
});

// Received fax callback (multipart with PDF file)
app.post('/webhook/received-fax', upload.single('file'), (req, res) => {
  const data = JSON.parse(req.body.recordDetails);
  console.log('From:', data.fromNumber);
  console.log('PDF saved to:', req.file?.path);
  res.status(200).send('OK');
});

// AI document callback (form-urlencoded)
app.post('/webhook/ai', (req, res) => {
  const data = JSON.parse(req.body.AiDocumentDetails);
  console.log('Classification:', data.aiResult.document_classification);
  res.status(200).send('OK');
});

Example: Sent Fax

Triggered after a fax finishes sending. Form field: recordDetails

JSON
// Form field: recordDetails
// Content-Type: application/x-www-form-urlencoded
{
  "fromNumber": "+15550100001",
  "toNumber": "+15550100002",
  "sendToken": "f5cfd99304f9",
  "sentAt": "2025-01-15T14:30:00.000Z",
  "completedAt": "2025-01-15T14:32:00.000Z",
  "sentStatus": "success",
  "numPages": 3,
  "errorDetails": null,
  "isTest": false,
  "secureLink": "https://storage.googleapis.com/medsender-example/..."
}
// Form field: recordDetails
// Content-Type: application/x-www-form-urlencoded
{
  "fromNumber": "+15550100001",
  "toNumber": "+15550100002",
  "sendToken": "f5cfd99304f9",
  "sentAt": "2025-01-15T14:30:00.000Z",
  "completedAt": "2025-01-15T14:32:00.000Z",
  "sentStatus": "success",
  "numPages": 3,
  "errorDetails": null,
  "isTest": false,
  "secureLink": "https://storage.googleapis.com/medsender-example/..."
}

Payload Fields

FieldDescription
sendTokenUnique identifier for this fax
fromNumberYour Medsender fax number that sent this fax
toNumberDestination fax number
sentStatusDelivery status: "success" or "failure"
sentAtWhen the fax was queued (ISO 8601)
completedAtWhen the fax finished sending (ISO 8601)
numPagesNumber of pages sent
errorDetailsError message if sentStatus is "failure"
isTestWhether this was a test fax
secureLinkURL to download the fax PDF

Example: Received Fax

Triggered after a fax is received on your number. Form field: recordDetails + file (PDF attachment)

JSON
// Form fields: recordDetails (JSON string) + file (PDF attachment)
// Content-Type: multipart/form-data
// All keys are camelCase
{
  "fromNumber": "+15550100002",
  "toNumber": "+15550100001",
  "sendToken": "887183cc2a5f",
  "callerName": "EXAMPLE CLINIC",
  "sentAt": "2025-01-15T09:15:00.000Z",
  "completedAt": "2025-01-15T09:16:00.000Z",
  "numPages": 3,
  "isTest": false,
  "faxStatus": "success",
  "errorDetails": null,
  "client": "client_001",
  "patientName": "John Doe",
  "patientDob": "1990-01-15",
  "callbackStatus": "success",
  "documentClassification": "Referral",
  "secondaryCategory": null,
  "patientFirstName": "John",
  "patientMiddleName": null,
  "patientLastName": "Doe",
  "insuranceMemberId": "MEM000000001",
  "referenceNumber": "REF-2025-001",
  "authorizationNumber": null,
  "codes": [{"code": "99213"}],
  "authDateRangeStart": null,
  "authDateRangeEnd": null,
  "denialReason": null
}
// Note: PDF file is also included as 'file' field
// AI extraction fields (documentClassification, patientName, etc.)
// are populated when that feature is enabled for your account
// Form fields: recordDetails (JSON string) + file (PDF attachment)
// Content-Type: multipart/form-data
// All keys are camelCase
{
  "fromNumber": "+15550100002",
  "toNumber": "+15550100001",
  "sendToken": "887183cc2a5f",
  "callerName": "EXAMPLE CLINIC",
  "sentAt": "2025-01-15T09:15:00.000Z",
  "completedAt": "2025-01-15T09:16:00.000Z",
  "numPages": 3,
  "isTest": false,
  "faxStatus": "success",
  "errorDetails": null,
  "client": "client_001",
  "patientName": "John Doe",
  "patientDob": "1990-01-15",
  "callbackStatus": "success",
  "documentClassification": "Referral",
  "secondaryCategory": null,
  "patientFirstName": "John",
  "patientMiddleName": null,
  "patientLastName": "Doe",
  "insuranceMemberId": "MEM000000001",
  "referenceNumber": "REF-2025-001",
  "authorizationNumber": null,
  "codes": [{"code": "99213"}],
  "authDateRangeStart": null,
  "authDateRangeEnd": null,
  "denialReason": null
}
// Note: PDF file is also included as 'file' field
// AI extraction fields (documentClassification, patientName, etc.)
// are populated when that feature is enabled for your account

Payload Fields

FieldDescription
sendTokenUnique identifier for this fax
fromNumberSender's fax number
toNumberYour Medsender fax number that received it
callerNameCaller ID name from sender
faxStatusReception status: "success" or "failure"
sentAtWhen transmission started (ISO 8601)
completedAtWhen reception completed (ISO 8601)
numPagesNumber of pages received
isTestWhether this was a test fax
clientClient ID if the number is assigned to a client
callbackStatusWebhook delivery status

AI Extraction Fields (when enabled for your account):

documentClassificationDocument type: "Referral", "Lab Result", etc.
patientNameFull patient name extracted
patientFirstNamePatient first name
patientLastNamePatient last name
patientDobPatient date of birth
insuranceMemberIdInsurance member ID
codesArray of medical codes (CPT, ICD, etc.)

Example: AI Document

Triggered after AI processing completes. Form field: AiDocumentDetails

Note: Keys inside aiResult use snake_case (known inconsistency).

JSON
// Form field: AiDocumentDetails
// Content-Type: application/x-www-form-urlencoded
// Note: Nested keys currently use snake_case (known issue)
{
  "aiResult": {
    "ai_token": "32b08347-6001-4e16-877b-7228975af105",
    "document_classification": "Referral",
    "patient_name": "John Doe",
    "patient_dob": "1990-01-15",
    "document_summary": "Cardiology referral for routine evaluation.",
    "completed_at": "2025-01-15T11:00:00.000Z"
  }
}
// Form field: AiDocumentDetails
// Content-Type: application/x-www-form-urlencoded
// Note: Nested keys currently use snake_case (known issue)
{
  "aiResult": {
    "ai_token": "32b08347-6001-4e16-877b-7228975af105",
    "document_classification": "Referral",
    "patient_name": "John Doe",
    "patient_dob": "1990-01-15",
    "document_summary": "Cardiology referral for routine evaluation.",
    "completed_at": "2025-01-15T11:00:00.000Z"
  }
}

Payload Fields (inside aiResult)

FieldDescription
ai_tokenUnique identifier for this AI document request
document_classificationDocument type: "Referral", "Lab Result", etc.
document_summaryAI-generated summary of the document
patient_nameFull patient name extracted
patient_dobPatient date of birth (YYYY-MM-DD)
patient_phone_numberPatient phone number
patient_genderPatient gender
insurance_member_idInsurance member ID
icd_codes_jsonArray of ICD codes
cpt_codes_jsonArray of CPT codes
completed_atWhen processing completed (ISO 8601)
error_detailsError message if processing failed