Documentation
SMS API Messages

Delivery reports

Receive message status by webhook and handle acknowledgements, retries and idempotency.

Set deliveryReportUrl when creating a batch to receive status updates for each recipient and status.

Webhook behavior

iP.1 sends each report with POST and expects 200 OK. The published retry policy is:

  • any response other than 200 OK triggers another attempt
  • retries are made at ten-minute intervals
  • no more than ten attempts are made
  • the report is removed after the final failed attempt

Respond quickly with 200 OK after validating the payload and placing it in an internal queue. Perform slow or failure-prone processing asynchronously.

Example payload

{
  "id": "message_abc123",
  "batchId": "batch_abc123",
  "owner": "ip1-XXXXX",
  "recipient": "46700000000",
  "code": 201,
  "duration": 0,
  "created": "2026-10-05T08:03:21Z",
  "segments": 1,
  "price": 0.0416,
  "currency": "SEK",
  "reference": "order-123"
}

The older source table contained two mislabeled rows (batchId for owner and recipient for sender). The payload example and fields below use the actual JSON keys.

Fields

Field Type Description
id string Message ID.
batchId string Parent batch ID.
owner string Owning SMS account, when included.
recipient string Recipient’s MSISDN.
code integer New status code.
duration integer Indicates whether the status is final according to the published model.
created string Status timestamp in ISO 8601.
segments integer Number of SMS segments.
price number Total message cost.
currency string Price currency.
reference string | null Your own reference from the batch.

Secure, idempotent receiver

  1. Allow HTTPS only.
  2. Limit the payload size.
  3. Validate Content-Type and the JSON schema.
  4. Use the combination of id, code and created as an idempotency key.
  5. Allow for the same report to arrive more than once.
  6. Compare event timestamps before overwriting a local status.
  7. Do not log complete phone numbers or message text unless required.
export async function POST({ request }) {
  const report = await request.json();

  if (!report.id || !Number.isInteger(report.code)) {
    return new Response('Invalid report', { status: 400 });
  }

  await queue.add('ip1-delivery-report', report);
  return new Response(null, { status: 200 });
}

The example demonstrates the principle; add authentication or network controls according to the options agreed for the integration.

See Status codes for the meaning of code and Messages for retrieving the complete history.

Choose the purposes you consent to. Necessary storage is used only to remember your choice.