Skip to content

Webhooks

Receive inbound messages, delivery receipts and opt-outs in real time, and verify that webhooks came from us.

Webhooks notify your application when something happens on your account, such as a reply arriving or a message being delivered. We send an HTTP request to a URL you choose each time the event occurs. You can have as many webhook subscriptions as you need.

Webhooks are the recommended way to receive replies and delivery receipts. Polling List inbound messages works, but is slower and uses up your rate limit.

Create a subscription

In MessagePort, open Settings in the sidebar, then Webhooks (or go to https://app.messageport.com.au/settings/webhooks). Click Create webhook subscription, or edit an existing one.

For each subscription you choose:

  • the event to subscribe to,
  • the URL to call,
  • the request method: POST or GET,
  • the encoding for POST: json or form,
  • up to 10 custom headers, for example an API key your endpoint expects.

You can also manage subscriptions over the API:

Task Endpoint
List subscriptions List webhook subscriptions
Create a subscription Create webhook subscription
Get one subscription Get webhook subscription
Update a subscription Update webhook subscription
Delete a subscription Delete webhook subscription
{
  "name": "Inbound to CRM",
  "event": "message_received",
  "url": "https://example.com/hooks/inbound",
  "request_method": "POST",
  "request_encoding_type": "json",
  "headers": [
    { "key": "X-Api-Key", "value": "abc123" }
  ]
}

A few things to know when using the API:

  • headers is an array of { "key", "value" } objects, not a plain object. It is required: send [] if you have none. Header names must be 3 to 50 characters, and values 3 to 200.
  • request_encoding_type is required when request_method is POST.
  • Updates replace the whole subscription, so send every field, not only the ones you’re changing.
  • The URL can’t point at one of our own domains.

Subscription status

Status Meaning
Active Enabled and receiving events.
Disabled Disabled manually.
Disabled Failing Disabled automatically after repeated delivery failures.

A subscription in Disabled Failing gets no events until you re-enable it. If your endpoint has been down, check for this once it’s back up.

Events

Event Sent when
message_received An inbound message (SMS, MMS or web chat) arrives on your account.
message_dlr The delivery status of a message you sent changes.
optout_created A number opts out.
message_received_3cx An inbound message arrives, formatted for the 3CX phone system integration. Use message_received for your own integrations.

message_received

Sent when an inbound message arrives, to a dedicated number or to the shared number pool.

{
  "event": "message_received",
  "date": "2022-01-13T04:23:57Z",
  "id": 123456,
  "to": "SHARED_NUMBER",
  "from": "61412345678",
  "content": "Hello Reply",
  "media": [],
  "is_opt_out": false,
  "channel": null,
  "in_reply_to_message": {
    "id": 654321,
    "content": "Hello World",
    "metadata": {
      "your_system_id": "c5969b2c-d33a-402d-9d7e-f82c98f85be7"
    },
    "reference": null,
    "campaign_id": 123456
  }
}
Field Description
event Always message_received.
date When the message was received.
id Inbound message ID.
to The dedicated number that received the message, or SHARED_NUMBER for the shared pool.
from The sender’s number.
content Message text. An empty string for an MMS sent without text.
media MMS media files. An empty array for SMS. See Inbound MMS media.
is_opt_out true if the message opted the sender out (for example, a STOP reply).
channel webchat if the message came from the web chat widget, null for SMS and MMS.
in_reply_to_message The outbound message this is a reply to, including its metadata, reference and campaign_id. null if no outbound message matched.

Inbound MMS media

For an MMS, media has one entry per file. Each entry has a key (the file’s unique, stable identifier) and a url (a direct link to the file). The payload doesn’t include the file contents.

"media": [
  { "key": "01gw171s65ecm8mhpx2086f7pg", "url": "https://mms.example.com/m/01gw171s65ecm8mhpx2086f7pg.jpeg" },
  { "key": "01gw171s65ecm8mhpx2086f7ph", "url": "https://mms.example.com/m/01gw171s65ecm8mhpx2086f7ph.png" }
]
  • Downloading needs no authentication. Fetch the url with a plain GET, with no API key or login.
  • Treat URLs as secrets. Anyone with a URL can fetch the file. Don’t forward links to untrusted recipients; attach the downloaded file instead.
  • Don’t assume a file type. Extensions reflect the type received from the carrier (commonly .jpeg, .png or .gif). Read the Content-Type header of the download.
  • Iterate the array. An MMS can contain several files, in the order the carrier sent them, so don’t just read media[0].
  • Download straight away and keep your own copy. Files are kept according to your account’s retention policy, and URLs aren’t guaranteed to resolve forever. If a download fails, retry promptly, and don’t return a success response until the media is stored.

List inbound messages returns the same media as media_urls, a flat array of URLs.

message_dlr

Sent each time the delivery status of a message you sent changes.

{
  "event": "message_dlr",
  "date": "2022-06-06T10:35:45Z",
  "id": 123456,
  "to": "61411802860",
  "from": "61419046139",
  "content": "Test",
  "parts": 1,
  "cost": 1,
  "reference": null,
  "metadata": {
    "your_system_id": "c5969b2c-d33a-402d-9d7e-f82c98f85be7"
  },
  "campaign_id": 618,
  "status": "delivered"
}
Field Description
event Always message_dlr.
date When the status changed.
id Outbound message ID.
to Recipient number.
from Sender ID the message was sent from, or SHARED_NUMBER for the shared pool.
content Message text.
parts Number of SMS parts.
cost Cost of the message.
reference, metadata The values you supplied when sending.
campaign_id Campaign ID, used in reporting.
status The new status. See below.

Delivery statuses

Treat any status starting with failed as a delivery failure.

Status Meaning
sent Handed to the carrier, awaiting a delivery receipt.
delivered Delivered to the handset (or, in some cases, to the recipient’s carrier).
failed Delivery failed.
failed_rejected The carrier rejected the message.
failed_expired The message couldn’t be delivered before it expired.
failed_insufficient_balance Your account balance was too low to send the message. Not billed.
cancelled The message was cancelled before sending. Not billed.
opt_out The recipient had opted out.
filtered Blocked by spam filtering, ours or upstream.
filtered_duplicate Filtered as a duplicate. Not billed.

optout_created

Sent each time a number opts out on your account: through a reply, an opt-out link, the web interface or the API. Imported opt-outs don’t trigger it.

{
  "event": "optout_created",
  "id": 123456,
  "number": "61419046139",
  "method": "reply",
  "country": {
    "id": 1,
    "iso": "AU",
    "name": "Australia"
  },
  "opt_out_list": {
    "id": 1,
    "name": "Default Opt-Out List",
    "is_default": true
  },
  "created": "2022-06-06T10:35:45Z"
}
Field Description
id Opt-out ID.
number The number that opted out.
method How the opt-out was created. See below.
country The number’s country: id, two-letter iso code and name.
opt_out_list The opt-out list it was added to.
created When the opt-out was created.
method Created by
reply An SMS reply, such as STOP.
link An opt-out link.
manual Someone in the web interface.
api The API.
chat The chat inbox.
workflow An automated workflow.

Responding and retries

Return a 2xx status as soon as you’ve safely stored the event. Any other response, or a timeout, counts as a failure.

Failed webhooks are retried up to 15 times, with exponentially increasing delays capped at 12 hours. Process events idempotently (for example, by event id and status), since the same event can arrive more than once.

Signature verification

Webhooks are signed with your account’s webhook secret, which you’ll find in your API settings in MessagePort. The signature is sent in the Signature header:

Signature: HMAC-SHA256(raw_json_request_body, webhook_secret)

Compute the HMAC-SHA256 of the raw request body with your secret and compare it to the header in constant time. In PHP:

$expected = hash_hmac('sha256', $rawBody, $webhookSecret);

if (!hash_equals($expected, $request->header('Signature'))) {
    abort(401);
}

In Node.js:

import crypto from 'node:crypto';

const expected = crypto.createHmac('sha256', webhookSecret).update(rawBody).digest('hex');
const valid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));

Use the raw body exactly as received. Parsing and re-serialising the JSON can change it and break the signature. For form-encoded and GET webhooks, the signature is still computed over the JSON encoding of the payload.

Legacy HTTP callbacks

Older accounts may use the legacy delivery receipt and incoming SMS callbacks configured in account settings. They still work, but new integrations should use webhook subscriptions.

Legacy callback parameters

Both callbacks are sent as POST requests with these body parameters.

Delivery receipt callback

Parameter Description
reference The reference you supplied when sending.
description DELIVERED or FAILED.
time When the receipt was generated, as YYYY-MM-DD HH:MM:SS.
timezone Timezone of time. Always your account’s timezone.

Incoming SMS callback

Parameter Description
reference The reference of the message being replied to.
message The reply text.
number The sender’s number, in international format.
time When the reply was received, as YYYY-MM-DD HH:MM:SS.
timezone Timezone of time. Always your account’s timezone.

Type to search…

↑↓ navigate↵ selectEsc close