The base URL for the API is https://api.mobilemessage.com.au/. This documentation will help you understand how to send and receive SMS messages, set up webhooks for real-time notifications, and track message delivery statuses.
The Mobile Message API allows up to 5 simultaneous requests per account. If you exceed this limit, you'll receive an HTTP 429 error with the message: "Too many concurrent requests. Please wait." Simply wait for an existing request to complete before trying again.
OpenAPI Specification
A complete OpenAPI 3.0 specification of the v1 API is available at /assets/openapi.json. Use it to generate typed clients, import the API into tools like Postman or Insomnia, or power your own request validation.
Build with your AI assistant
Our API docs are available as plain Markdown, so ChatGPT, Claude, Cursor, Copilot and other coding tools can read them directly. Copy the docs into your tool, open them in a chat, or paste the integration prompt below to get accurate, working code first time.
Copy an integration prompt for your AI tool
I'm integrating the Mobile Message SMS API (Australia). Before writing any code, read the full API reference at https://mobilemessage.com.au/llms-full.txt. The OpenAPI 3.0 spec is at https://mobilemessage.com.au/assets/openapi.json if you need exact schemas. Follow these rules: 1. Only use endpoints, parameters and response fields that appear in that documentation. Never invent or guess. If the docs don't cover something, say so and point me to the relevant section or to https://help.mobilemessage.com.au/. 2. Base URL is https://api.mobilemessage.com.au/. Auth is HTTP Basic using an API username and password created in the dashboard (app.mobilemessage.com.au, Settings > API). Use my language's native Basic-auth helper rather than hand-building the header. 3. Send with POST /v1/messages (up to 10,000 messages per request). Always include an Idempotency-Key header (a UUID per batch) so a retry after a timeout can't double-send. 4. The account allows 5 concurrent requests. HTTP 429 means wait for an in-flight request to finish, then retry. There is no per-second rate limit. 5. Australian mobile numbers only. Every timestamp the API accepts or returns is UTC. 6. The sender must be a sender ID already approved on the account. Use GET /v1/senders to list them; don't assume one exists. 7. Emojis and other non-GSM characters need enable_unicode set to true, which switches to UCS-2 and shortens each part from 153 to 67 characters. Mention this whenever message length matters. 8. Webhook handlers (inbound messages and delivery receipts) must return 2xx within 5 seconds and be idempotent, because deliveries are retried and can arrive out of order. 9. Handle errors by status: 401 credentials, 400 malformed request, 403 insufficient credits, 429 concurrency, 5xx retry with the same Idempotency-Key. 10. New accounts include 50 free credits, so I can test with real messages straight away. If I need to test without delivering to handsets, a sandbox account is available from hello@mobilemessage.com.au, and on a sandbox, numbers ending in 000 simulate a failed delivery. Give me a minimal working example in my language, then ask what I'm building.
Throughput
Batches are accepted at up to 10,000 messages per request with 5 concurrent requests. Accepted messages are queued and submitted to Australian carriers at a sustained rate of over 400 messages per second — a 50,000-message campaign is typically fully submitted to carriers within about two minutes. There is no per-second cap on API submissions; HTTP 429 is only returned for more than 5 concurrent requests.
Authentication
Use Basic Authentication with your API username and password to access the endpoints. Follow these steps:
- Combine your
username:password. - Encode the resulting string in Base64.
- Add this string to the
Authorizationheader as:Authorization: Basic {base64_encoded_credentials}.
Multiple API Keys and Rotation
Your account can hold multiple active API keys at the same time, managed in your dashboard under Settings > API. To rotate a credential with zero downtime: create a new key, update your application to use it, confirm traffic has switched over on the API logs page in your dashboard, then delete the old key. Each key is independent, so the old key keeps working until the moment you delete it.
Code Examples
curl -u user123:mypassword -X GET https://api.mobilemessage.com.au/v1/messages
import requests
from requests.auth import HTTPBasicAuth
response = requests.get('https://api.mobilemessage.com.au/v1/messages',
auth=HTTPBasicAuth('user123', 'mypassword'))
print(response.json())
const username = 'user123';
const password = 'mypassword';
fetch('https://api.mobilemessage.com.au/v1/messages', {
headers: {
'Authorization': 'Basic ' + btoa(`${username}:${password}`)
}
})
.then(response => response.json())
.then(data => console.log(data));
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/messages');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class APIRequest {
public static void main(String[] args) throws Exception {
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/messages");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Authorization", "Basic " + credentials);
connection.setRequestMethod("GET");
// Process the response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var byteArray = Encoding.ASCII.GetBytes("user123:mypassword");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
var response = await client.GetAsync("https://api.mobilemessage.com.au/v1/messages");
Console.WriteLine(await response.Content.ReadAsStringAsync());
}
}
Send SMS Messages
POST /v1/messages
This endpoint allows you to send one or more SMS messages. You can include up to 10,000 messages in a single request.
For automated integrations — particularly those sending large batches or implementing retry logic — we recommend including an Idempotency-Key header so that retries after a network error or timeout don't risk duplicate sends. Replayed requests return the original response with an Idempotency-Replay: true header; reusing a key with a different request payload returns HTTP 422. Keys are scoped to the API key that sent them and are kept for 24 hours. See Safely retrying API requests with an Idempotency-Key for details.
Top-level parameters
| Parameter | Type | Description |
|---|---|---|
messages |
Array of Objects | One or more message objects. |
enable_unicode (optional) |
Boolean | When true, messages that require UCS-2 (for example emojis or non-GSM characters) are sent using UCS-2. Defaults to false. Each message can still set unicode individually. |
max_parts (optional) |
Integer | Maximum SMS parts (credits) per message, applied to all messages in the batch. Messages exceeding this limit are skipped with status error. Default 10, range 1–99. |
ignore_unsubscribes (optional) |
Boolean |
Set to true to bypass unsubscribe blocking for this send request.
If omitted or set to false, normal unsubscribe blocking applies.
Use with caution, as bypassing your unsubscribe list could result in spam complaints.
|
shorten_urls (optional) |
Boolean | Replace the links in every message in this request with short trackable links so clicks can be reported. Set it to "auto" to shorten whenever your account is able to, and send with the original links instead of an error when it is not. If omitted, your account default applies, which you can change in your dashboard under Settings. Each message can override this with its own shorten_urls field. See Link shortening below. |
Message object fields
| Field | Type | Description |
|---|---|---|
to |
String | An Australian mobile number, in local (0412345678) or international (61412345678) format. Any other number is rejected with status error at no cost. |
message |
String |
The message content. Up to 10 parts are supported: • GSM-7: up to 1530 characters (10 × 153) • UCS-2: up to 670 characters (10 × 67) If Unicode is not enabled, non-GSM characters are stripped. Supports placeholders: {first_name}, {last_name}, {company}, {other}, {field_1} through {field_5}, and {optout}. The contact placeholders are filled from the recipient's saved contact record (if one exists). See below for details.
|
sender |
String | The mobile number or business name the message will come from. Must be a Sender ID registered in your account — use GET /v1/senders to list your available senders. |
custom_ref (optional) |
String | A custom reference to help track the message. |
unicode (optional) |
Boolean | Overrides the top-level enable_unicode for this message. When true, the message will be sent using UCS-2 if required. |
scheduled_for (optional) |
String | UTC datetime to send this message (ISO 8601, e.g. 2026-04-01T09:00:00). Must be more than 1 minute in the future — values at or before that (including past datetimes) are sent immediately rather than rejected. If omitted, the message is sent immediately. Scheduled messages can be cancelled via DELETE /v1/messages. |
shorten_urls (optional) |
Boolean | Overrides the top-level shorten_urls for this message. Set it to false to leave one message in a batch with its original links, or "auto" to shorten this message only when the account is able to. |
{first_name}, {last_name}, {company}, {other}, and {field_1} through {field_5} will be replaced with the values from that contact record. If you have named your custom fields in the app (e.g. "Date of Birth"), you can also use the slug form {date_of_birth}. If no matching contact is found, placeholders are replaced with an empty string.
{optout} token: Include {optout} anywhere in your message body and it will automatically be replaced with an opt-out instruction before sending. The replacement is always exactly 20 characters:
- Dedicated or shared number senders →
Opt out: Reply Stop - Alphanumeric or own-number senders →
OptOut mb.st/XXXXXX(whereXXXXXXis your account's unique 6-character opt-out code)
shorten_urls is on, each link is replaced before the message parts are counted, so length is measured on the short link rather than your original URL. The replacement is a fixed length:
- First link →
https://addr.au/XXXXXX, exactly 22 characters - Second and third links →
https://addr.au/XXXXXX2andhttps://addr.au/XXXXXX3, 23 characters each
Link shortening
With shorten_urls enabled, every link in the message is replaced with a short link on addr.au that is unique to that recipient, so clicks can be attributed to the individual message. Clicks appear on the send report in your dashboard and through GET /v1/link-tracking. Omitting the flag falls back to your account default, which is how integrations that cannot set a flag opt in.
shorten_urls accepts a third value, "auto". With "auto", the message is shortened whenever the account and the message qualify, and otherwise sent with its original links instead of an error. Check for the short_links object in each result to see whether a message was shortened. Use "auto" when a send must never fail over shortening, such as an automated integration. The rules below describe an explicit shorten_urls: true; with "auto", every case listed as an error sends unshortened instead.
- Link shortening is available once your account has made its first credit purchase.
- Up to 3 different links per message. A message with more comes back with status
errorand is not sent. - A link containing a personalisation tag such as
{first_name}cannot be shortened, because it resolves to a different destination for every recipient. Remove the tag from the link or turn shortening off for that message. - Each link can be up to 2048 characters long.
- Short links keep working for 90 days from the send, then stop resolving.
- The credit cost is calculated on the shortened message, so a long URL that fits into 22 characters can reduce the number of parts.
A shortened message carries a short_links object in its result, listing the code issued for that recipient and each original link with the short link that replaced it.
{
"to": "0412345678",
"message": "Hi Sam, our sale starts today https://addr.au/Xk3PqR",
"sender": "CompanyABC",
"status": "success",
"cost": 1,
"message_id": "abcd1234-efgh-5678-ijkl-9876543210mn",
"short_links": {
"code": "Xk3PqR",
"links": [
{ "url": "https://example.com/spring-sale", "short_url": "https://addr.au/Xk3PqR" }
]
}
}
If short links cannot be issued at the moment of sending, the messages still go out with their original links and the response includes "link_tracking": "unavailable" at the top level. The send succeeds; only the tracking is missing.
Code examples
curl -u user123:mypassword -X POST https://api.mobilemessage.com.au/v1/messages \
-H "Content-Type: application/json" \
-d '{
"enable_unicode": true,
"messages": [
{
"to": "0412345678",
"message": "Hello, this is a test message",
"sender": "CompanyABC",
"custom_ref": "tracking001"
},
{
"to": "0412345679",
"message": "Hello 🌏 from Mobile Message",
"sender": "CompanyABC",
"custom_ref": "tracking002",
"unicode": true
}
]
}'
import requests
from requests.auth import HTTPBasicAuth
data = {
"enable_unicode": True,
"messages": [
{
"to": "0412345678",
"message": "Hello, this is a test message",
"sender": "CompanyABC",
"custom_ref": "tracking001"
},
{
"to": "0412345679",
"message": "Hello 🌏 from Mobile Message",
"sender": "CompanyABC",
"custom_ref": "tracking002",
"unicode": true
}
]
}
response = requests.post('https://api.mobilemessage.com.au/v1/messages',
json=data, auth=HTTPBasicAuth('user123', 'mypassword'))
print(response.json())
const body = {
enable_unicode: true,
messages: [
{
to: "0412345678",
message: "Hello, this is a test message",
sender: "CompanyABC",
custom_ref: "tracking001"
},
{
to: "0412345679",
message: "Hello 🌏 from Mobile Message",
sender: "CompanyABC",
custom_ref: "tracking002",
unicode: true
}
]
};
fetch('https://api.mobilemessage.com.au/v1/messages', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
})
.then(r => r.json())
.then(console.log);
<?php
$data = [
"enable_unicode" => true,
"messages" => [
[
"to" => "0412345678",
"message" => "Hello, this is a test message",
"sender" => "CompanyABC",
"custom_ref" => "tracking001"
],
[
"to" => "0412345679",
"message" => "Hello 🌏 from Mobile Message",
"sender" => "CompanyABC",
"custom_ref" => "tracking002",
"unicode" => true
]
]
];
$ch = curl_init('https://api.mobilemessage.com.au/v1/messages');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class SendSMS {
public static void main(String[] args) throws Exception {
String json = """
{
"enable_unicode": true,
"messages": [
{"to":"0412345678","message":"Hello, this is a test message","sender":"CompanyABC","custom_ref":"tracking001"},
{"to":"0412345679","message":"Hello 🌏 from Mobile Message","sender":"CompanyABC","custom_ref":"tracking002","unicode":true}
]
}""";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/messages");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("POST");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
class Program {
static async Task Main() {
var body = new {
enable_unicode = true,
messages = new[] {
new { to = "0412345678", message = "Hello, this is a test message", sender = "CompanyABC", custom_ref = "tracking001" },
new { to = "0412345679", message = "Hello 🌏 from Mobile Message", sender = "CompanyABC", custom_ref = "tracking002", unicode = true }
}
};
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
var resp = await client.PostAsync("https://api.mobilemessage.com.au/v1/messages", content);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
Response example
{
"status": "complete",
"send_id": 42,
"ignore_unsubscribes": false,
"shorten_urls": false,
"total_cost": 2,
"results": [
{
"to": "0412345678",
"message": "Hello, this is a test message",
"sender": "CompanyABC",
"custom_ref": "tracking001",
"status": "success",
"cost": 1,
"message_id": "abcd1234-efgh-5678-ijkl-9876543210mn",
"encoding": "gsm7"
},
{
"to": "0412345679",
"message": "Hello 🌏 from Mobile Message",
"sender": "CompanyABC",
"custom_ref": "tracking002",
"status": "success",
"cost": 1,
"message_id": "abcd1234-efgh-5678-ijkl-9876543210xy",
"encoding": "ucs2"
}
]
}
encoding appears only if Unicode was enabled in the request (either top-level or per message). The top-level send_id is only present when the request contains 2 or more valid messages. Per-message status can also be "blocked" when the recipient has unsubscribed. Entries that are structurally invalid (not an object, or missing to, message, or sender) come back with only status, error, and item (the zero-based index in your request) — match those errors to your input by item, not by recipient. shorten_urls echoes the setting the request ran with, and a shortened message also carries a short_links object described under Link shortening.
Send to List
POST /v1/list-send
Send an SMS to all contacts in a list in a single request. Supports per-contact variable substitution, {optout}, and scheduled delivery. Unsubscribed numbers are automatically filtered unless ignore_unsubscribes is set.
Use GET /v1/messages?custom_ref=your-ref to check delivery status per recipient after sending, or GET /v1/sends for the totals of the whole send.
| Field | Type | Description |
|---|---|---|
list_id | Integer | ID of the list to send to. |
sender | String | Your approved Sender ID. Use GET /v1/senders to list your available senders. |
message | String | Message content. Supports {first_name}, {last_name}, {company}, {other}, {field_1}–{field_5} (or custom name slugs), and {optout}. Each contact's details are substituted individually. |
enable_unicode (optional) | Boolean | When true, messages containing emojis or non-GSM characters are sent as UCS-2. Defaults to false (non-GSM characters are stripped). |
max_parts (optional) | Integer | Maximum SMS parts (credits) per message. Messages that exceed this limit are skipped and counted in total_skipped_too_long. Default 10, range 1–99. |
custom_ref (optional) | String | Reference stored against every message in this send. Reusing a reference your account already used for a list send in the last 24 hours returns HTTP 409, so a retried request cannot send the same campaign twice. |
ignore_unsubscribes (optional) | Boolean | Bypass unsubscribe filtering. Default false. |
scheduled_for (optional) | String | UTC datetime to send (ISO 8601). Must be more than 1 minute in the future — values at or before that (including past datetimes) are sent immediately rather than rejected. If omitted, sends immediately. A scheduled list send can be cancelled in bulk by passing the custom_ref to DELETE /v1/messages. |
shorten_urls (optional) | Boolean or "auto" | Replace the links in the message with short trackable links, one set of codes per recipient, so clicks can be reported. Set it to "auto" to shorten whenever the account is able to, and send with the original links instead of an error when it is not. If omitted, your account default applies. A list send uses one message template, so the setting applies to the whole send. See Link shortening for the rules, and GET /v1/link-tracking for the results. |
stagger_minutes (optional) | Integer | Spread sending over this many minutes. Messages are divided into batches and sent gradually over the period. Requires 50+ recipients — with fewer, the value is silently ignored and the send goes out at once. Can be combined with scheduled_for to stagger from a future time. |
curl -u user123:mypassword -X POST https://api.mobilemessage.com.au/v1/list-send \
-H "Content-Type: application/json" \
-d '{
"list_id": 5,
"sender": "CompanyABC",
"message": "Hi {first_name}, our sale starts today.{optout}",
"custom_ref": "march-sale"
}'
import requests
from requests.auth import HTTPBasicAuth
data = {
"list_id": 5,
"sender": "CompanyABC",
"message": "Hi {first_name}, our sale starts today.{optout}",
"custom_ref": "march-sale"
}
response = requests.post('https://api.mobilemessage.com.au/v1/list-send',
json=data, auth=HTTPBasicAuth('user123', 'mypassword'))
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/list-send', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
list_id: 5,
sender: 'CompanyABC',
message: 'Hi {first_name}, our sale starts today.{optout}',
custom_ref: 'march-sale'
})
})
.then(r => r.json())
.then(console.log);
<?php
$data = [
"list_id" => 5,
"sender" => "CompanyABC",
"message" => "Hi {first_name}, our sale starts today.{optout}",
"custom_ref" => "march-sale"
];
$ch = curl_init('https://api.mobilemessage.com.au/v1/list-send');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class SendToList {
public static void main(String[] args) throws Exception {
String json = "{\"list_id\":5,\"sender\":\"CompanyABC\","
+ "\"message\":\"Hi {first_name}, our sale starts today.{optout}\","
+ "\"custom_ref\":\"march-sale\"}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/list-send");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("POST");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var json = "{\"list_id\":5,\"sender\":\"CompanyABC\","
+ "\"message\":\"Hi {first_name}, our sale starts today.{optout}\","
+ "\"custom_ref\":\"march-sale\"}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var resp = await client.PostAsync("https://api.mobilemessage.com.au/v1/list-send", content);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{
"status": "complete",
"send_id": 42,
"list_id": 5,
"total_recipients": 150,
"total_blocked_unsubscribed": 3,
"total_skipped_too_long": 0,
"total_cost": 150,
"scheduled_for": null,
"stagger_minutes": null,
"send_status": "queued",
"shorten_urls": true,
"tracked_urls": ["https://example.com/spring-sale"]
}
send_status is "queued" for immediate sends, "scheduled" for future sends, or "staggered" when a stagger was applied. stagger_minutes echoes the stagger period, or null when not staggered. shorten_urls echoes the setting the send ran with, and tracked_urls lists the destinations now being tracked, in the order they appear in the message. Each recipient gets their own short link for each of those destinations, so the short links themselves are per message rather than per send.
Because a list send has one template, link shortening problems are returned for the whole request rather than per recipient. A request that sets shorten_urls to true when the account is not eligible returns HTTP 403 with "Link shortening is available after your first credit purchase.", and an explicit request with more than 3 links, or a link containing a personalisation tag, returns HTTP 400 with the reason. When the value is "auto", or shortening comes only from your account default, these cases send the message unchanged with its original links instead of returning an error. If short links cannot be issued at the moment of sending, the send still goes out with the original links and the response includes "link_tracking": "unavailable".
Send Status
GET /v1/sends
Read the status and running totals of one send, as returned by POST /v1/messages or POST /v1/list-send. The delivery, failure, reply and unsubscribe counts are the same figures the Mobile Message app reports for the send, so a campaign can be tracked with one request instead of paging through every recipient.
Query parameters
| Parameter | Type | Description |
|---|---|---|
send_id | Integer | The send to report on. Required. |
Code Examples
curl -u user123:mypassword \
"https://api.mobilemessage.com.au/v1/sends?send_id=4821"
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
'https://api.mobilemessage.com.au/v1/sends',
params={'send_id': 4821},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/sends?send_id=4821', {
headers: { 'Authorization': 'Basic ' + btoa('user123:mypassword') }
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/sends?send_id=4821');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, 'user123:mypassword');
$response = curl_exec($ch);
curl_close($ch);
echo $response;
URL url = new URL("https://api.mobilemessage.com.au/v1/sends?send_id=4821");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
String auth = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
conn.setRequestProperty("Authorization", "Basic " + auth);
try (BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
}
using var client = new HttpClient();
var auth = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", auth);
var resp = await client.GetAsync("https://api.mobilemessage.com.au/v1/sends?send_id=4821");
Console.WriteLine(await resp.Content.ReadAsStringAsync());
Response
{
"send_id": 4821,
"status": "sent",
"total_recipients": 4286,
"total_blocked_unsubscribed": 37,
"delivered_count": 4198,
"failed_count": 61,
"reply_count": 14,
"unsub_count": 9,
"total_cost": 4286,
"scheduled_for": null,
"created_at": "2026-09-15 01:12:44"
}
| Field | Type | Description |
|---|---|---|
send_id | Integer | The send. |
status | String | processing, scheduled, sending, sent, cancelled or partial. See the table below. |
total_recipients | Integer | Recipients in the send. |
total_blocked_unsubscribed | Integer or null | Recipients blocked due to unsubscribe, for sends made through POST /v1/list-send. Null for sends made another way. |
delivered_count | Integer | Messages confirmed delivered by the carrier. |
failed_count | Integer | Messages the carrier could not deliver. |
reply_count | Integer | Replies received to the send. |
unsub_count | Integer | Recipients who opted out after the send. |
total_cost | Integer | Credits consumed. Zero once a send has been cancelled and refunded. |
scheduled_for | String or null | The scheduled datetime in UTC, or null if the send went out immediately. |
created_at | String | When the send was created, in UTC. |
Send statuses
| Status | Meaning |
|---|---|
processing | A large list send that has been accepted, charged and is being prepared. It becomes scheduled, sending or sent on its own, usually within a minute, and the counts stay at zero until it does. |
scheduled | The send is waiting for its scheduled time, or for its stagger batches. |
sending | The send is on its way. |
sent | Every message has been handed to the carrier. |
cancelled | The send was cancelled and its unused credits returned. |
partial | Some messages in the send were cancelled and the rest were sent. |
Errors
| Status | Error | Cause |
|---|---|---|
| 400 | "send_id" is required. | send_id was missing, zero or not a number. |
| 404 | Send not found. | No send with that ID exists on your account. |
| 405 | Invalid request method. Only GET is allowed. | The endpoint was called with a method other than GET. |
Message History
GET /v1/messages
Retrieve sent message history. You can look up a specific message by ID or custom reference, or browse paginated history with optional filters.
Lookup by ID or reference
| Parameter | Type | Description |
|---|---|---|
message_id |
String | The unique message ID (UUID) returned when the message was sent. |
custom_ref |
String | Your custom reference (exact match). Returns all messages with this reference. |
Paginated history
When neither message_id nor custom_ref is provided, returns paginated outbound message history.
| Parameter | Type | Description |
|---|---|---|
status (optional) |
String | Filter by status. One of: pending, scheduled, sent, delivered, failed, cancelled. |
from (optional) |
String | Filter messages sent on or after this date (YYYY-MM-DD, UTC). |
to (optional) |
String | Filter messages sent before this date (YYYY-MM-DD, UTC). |
limit (optional) |
Integer | Results per page. Default 50, max 200. |
offset (optional) |
Integer | Pagination offset. Default 0. |
Code Examples
curl -u user123:mypassword -X GET "https://api.mobilemessage.com.au/v1/messages?message_id=abcd1234-efgh-5678-ijkl-9876543210mn"
import requests
from requests.auth import HTTPBasicAuth
url = "https://api.mobilemessage.com.au/v1/messages?message_id=abcd1234-efgh-5678-ijkl-9876543210mn"
response = requests.get(url, auth=HTTPBasicAuth('user123', 'mypassword'))
print(response.json())
const messageId = "abcd1234-efgh-5678-ijkl-9876543210mn";
fetch(`https://api.mobilemessage.com.au/v1/messages?message_id=${messageId}`, {
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword')
}
})
.then(response => response.json())
.then(data => console.log(data));
<?php
$message_id = "abcd1234-efgh-5678-ijkl-9876543210mn";
$url = "https://api.mobilemessage.com.au/v1/messages?message_id=" . $message_id;
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class LookupMessage {
public static void main(String[] args) throws Exception {
String message_id = "abcd1234-efgh-5678-ijkl-9876543210mn";
URL url = new URL("https://api.mobilemessage.com.au/v1/messages?message_id=" + message_id);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
connection.setRequestProperty("Authorization", "Basic " + credentials);
connection.setRequestMethod("GET");
// Process the response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
class Program {
static async Task Main() {
string message_id = "abcd1234-efgh-5678-ijkl-9876543210mn";
string url = $"https://api.mobilemessage.com.au/v1/messages?message_id={message_id}";
var client = new HttpClient();
var byteArray = System.Text.Encoding.ASCII.GetBytes("user123:mypassword");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
var response = await client.GetAsync(url);
Console.WriteLine(await response.Content.ReadAsStringAsync());
}
}
Response — lookup by ID or reference
{
"status": "complete",
"results": [
{
"to": "61412345678",
"message": "Hello, this is message 1",
"sender": "CompanyABC",
"custom_ref": "tracking001",
"status": "delivered",
"cost": "1.00",
"message_id": "abcd1234-efgh-5678-ijkl-9876543210mn",
"requested_at": "2026-01-15 09:35:00",
"scheduled_for": null,
"send_id": 42
}
]
}
Note the lookup response uses different field names to the paginated history response below. cost is returned as a decimal string on GET endpoints, and send_id is only present when the message was part of a batch.
Response — paginated history
curl -u user123:mypassword "https://api.mobilemessage.com.au/v1/messages?status=delivered&from=2026-01-01&limit=5"
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
'https://api.mobilemessage.com.au/v1/messages',
params={'status': 'delivered', 'from': '2026-01-01', 'limit': 5},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/messages?status=delivered&from=2026-01-01&limit=5', {
headers: { 'Authorization': 'Basic ' + btoa('user123:mypassword') }
})
.then(r => r.json())
.then(console.log);
<?php
$url = 'https://api.mobilemessage.com.au/v1/messages?status=delivered&from=2026-01-01&limit=5';
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
echo curl_exec($ch);
curl_close($ch);
?>
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class MessageHistory {
public static void main(String[] args) throws Exception {
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/messages?status=delivered&from=2026-01-01&limit=5");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestProperty("Authorization", "Basic " + credentials);
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var resp = await client.GetAsync("https://api.mobilemessage.com.au/v1/messages?status=delivered&from=2026-01-01&limit=5");
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{
"status": "complete",
"total": 120,
"limit": 5,
"offset": 0,
"results": [
{
"message_id": "abcd1234-efgh-5678-ijkl-9876543210mn",
"recipient_number": "61412345678",
"sender_id": "CompanyABC",
"message_content": "Hello!",
"status": "delivered",
"cost": "1.00",
"custom_ref": "tracking001",
"requested_at": "2026-01-15 09:35:00",
"scheduled_for": null,
"send_id": 42
}
]
}
Link Tracking
GET /v1/link-tracking
Read the click results for messages that went out with short links. Provide exactly one of send_id, message_id or custom_ref. With send_id the totals cover the whole send, and include=recipients adds a page of per-recipient rows so you can see exactly who clicked. With message_id or custom_ref the results cover that single message, which is how you read the clicks on a message sent on its own.
Automated link scanners in carrier and inbox security products fetch a link as soon as it is delivered. Those hits are recorded but excluded from every figure below, so the numbers reflect people rather than software.
Query parameters
| Parameter | Type | Description |
|---|---|---|
send_id | Integer | The send to report on, as returned by POST /v1/messages or POST /v1/list-send. Provide exactly one of send_id, message_id or custom_ref. |
message_id | String | The UUID of a single message, as returned when it was sent. Returns the click results for that message alone. |
custom_ref | String | Your own reference on a single message. Where several messages carry the same reference, the most recent one is used. |
include (optional) | String | Set to recipients to include a page of per-recipient results. Any other value is ignored. Only applies with send_id. |
clicked (optional) | Boolean | Set to true to return only the recipients who clicked, instead of every recipient. recipients_total then counts the clickers, and paging works the same way. Only applies with include=recipients. |
limit (optional) | Integer | Recipients per page. Default 50, maximum 200. Only applies with include=recipients. |
offset (optional) | Integer | Recipients to skip, for paging through a large send. Default 0. |
curl -u user123:mypassword "https://api.mobilemessage.com.au/v1/link-tracking?send_id=42&include=recipients&limit=50"
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
'https://api.mobilemessage.com.au/v1/link-tracking',
params={'send_id': 42, 'include': 'recipients', 'limit': 50},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/link-tracking?send_id=42&include=recipients&limit=50', {
headers: { 'Authorization': 'Basic ' + btoa('user123:mypassword') }
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/link-tracking?send_id=42&include=recipients&limit=50');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
echo curl_exec($ch);
curl_close($ch);
?>
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class LinkTracking {
public static void main(String[] args) throws Exception {
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/link-tracking?send_id=42&include=recipients&limit=50");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestProperty("Authorization", "Basic " + credentials);
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var resp = await client.GetAsync("https://api.mobilemessage.com.au/v1/link-tracking?send_id=42&include=recipients&limit=50");
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
Response example
{
"status": "complete",
"send_id": 42,
"link_tracking": true,
"total_recipients": 150,
"delivered_count": 148,
"total_clicks": 96,
"unique_clicks": 71,
"click_rate": 0.4797,
"urls": [
{
"url_set_id": 18,
"url_index": 1,
"url": "https://example.com/spring-sale",
"clicks": 96,
"unique_clicks": 71
}
],
"recipients_total": 150,
"limit": 50,
"offset": 0,
"recipients": [
{
"message_id": "abcd1234-efgh-5678-ijkl-9876543210mn",
"to": "61412345678",
"clicked": true,
"click_count": 2,
"first_click": "2026-04-01 09:14:22",
"last_click": "2026-04-01 11:02:47"
}
]
}
total_clicks counts every click, unique_clicks counts recipients who clicked at least once, and click_rate is unique_clicks divided by delivered_count, rounded to 4 decimal places. click_rate is null when nothing has been delivered yet. Click times are UTC. The recipients_total, limit, offset and recipients fields appear only with include=recipients, and message_id matches the value returned when the message was sent.
Sends with more than one set of links
A POST /v1/messages batch can carry different links in different messages. Every distinct combination of links in the batch is recorded as its own URL set, and urls returns one entry for each link in each set. Use url_set_id to tell the sets apart. url_index is only the position of a link inside its own set, so url_index 1 of one set and url_index 1 of another are different destinations. Entries are ordered by url_set_id, then by url_index.
A POST /v1/list-send sends one message template to the whole list, so it always has a single URL set.
{
"status": "complete",
"send_id": 44,
"link_tracking": true,
"total_recipients": 300,
"delivered_count": 297,
"total_clicks": 184,
"unique_clicks": 140,
"click_rate": 0.4714,
"urls": [
{
"url_set_id": 21,
"url_index": 1,
"url": "https://example.com/winter-sale",
"clicks": 112,
"unique_clicks": 88
},
{
"url_set_id": 22,
"url_index": 1,
"url": "https://example.com/new-range",
"clicks": 49,
"unique_clicks": 38
},
{
"url_set_id": 22,
"url_index": 2,
"url": "https://example.com/help",
"clicks": 23,
"unique_clicks": 21
}
]
}
The send totals at the top of the response cover the whole send across every set. unique_clicks on the send counts each recipient once even if they clicked links in more than one position, so the per-URL unique_clicks figures can add up to more than the send total.
Tracking a single message
A send is only created for a batch of two or more messages, so a message sent on its own has no send_id. Look it up with message_id or custom_ref instead and the results cover that one message. include, limit and offset describe a page of recipients, so they are ignored in this form.
curl -u user123:mypassword "https://api.mobilemessage.com.au/v1/link-tracking?message_id=7f3c1a94-5b62-4f1e-9c0d-2ea84b6d5713"
{
"status": "complete",
"message_id": "7f3c1a94-5b62-4f1e-9c0d-2ea84b6d5713",
"custom_ref": "order-10482",
"to": "61412345678",
"message_status": "delivered",
"link_tracking": true,
"clicked": true,
"click_count": 2,
"first_click": "2026-04-01 09:14:22",
"last_click": "2026-04-01 11:02:47",
"urls": [
{
"url_index": 1,
"url": "https://example.com/order/10482",
"clicks": 2
}
]
}
custom_ref appears only when the message was sent with one. The delivery state of the message is returned as message_status, because the top-level status field always reports on the request itself. click_count counts every click from this recipient across all of its links, and first_click and last_click are null until they click. Click times are UTC.
Sends without link tracking
A send that did not use short links is not an error. It returns a short answer with link_tracking set to false and no click figures.
{
"status": "complete",
"send_id": 43,
"link_tracking": false,
"total_recipients": 150
}
A single message that went out without short links answers the same way.
{
"status": "complete",
"message_id": "7f3c1a94-5b62-4f1e-9c0d-2ea84b6d5713",
"custom_ref": "order-10482",
"link_tracking": false
}
Errors
| Status | Error | Cause |
|---|---|---|
| 400 | "send_id", "message_id", or "custom_ref" is required. | None of the three was provided. |
| 400 | Provide only one of "send_id", "message_id", or "custom_ref". | More than one of the three was provided. |
| 400 | "send_id" is required. | send_id was zero or not a number. |
| 404 | Send not found. | No send with that ID exists on your account. |
| 404 | Message not found. | No message with that ID or reference exists on your account. |
| 405 | Invalid request method. Only GET is allowed. | The endpoint was called with a method other than GET. |
Cancel Scheduled Message
DELETE /v1/messages
Cancel one or more scheduled messages and receive a full credit refund. Only messages with status=scheduled can be cancelled. Provide message_id, custom_ref, or send_id.
Request body
| Field | Type | Description |
|---|---|---|
message_id | String | The UUID of a single scheduled message to cancel. |
custom_ref | String | Cancel all scheduled messages with this custom reference (useful for cancelling a batch). |
send_id | Integer | Cancel all scheduled messages in a bulk send. The send_id is returned when sending to multiple recipients via POST. |
Cancel by message_id
curl -u user123:mypassword -X DELETE https://api.mobilemessage.com.au/v1/messages \
-H "Content-Type: application/json" \
-d '{"message_id":"abcd1234-efgh-5678-ijkl-9876543210mn"}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.delete(
'https://api.mobilemessage.com.au/v1/messages',
json={"message_id": "abcd1234-efgh-5678-ijkl-9876543210mn"},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/messages', {
method: 'DELETE',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ message_id: 'abcd1234-efgh-5678-ijkl-9876543210mn' })
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/messages');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["message_id" => "abcd1234-efgh-5678-ijkl-9876543210mn"]));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class CancelMessage {
public static void main(String[] args) throws Exception {
String json = "{\"message_id\":\"abcd1234-efgh-5678-ijkl-9876543210mn\"}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/messages");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("DELETE");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var json = "{\"message_id\":\"abcd1234-efgh-5678-ijkl-9876543210mn\"}";
var request = new HttpRequestMessage(HttpMethod.Delete, "https://api.mobilemessage.com.au/v1/messages") {
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
var resp = await client.SendAsync(request);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "complete", "message_id": "abcd1234-efgh-5678-ijkl-9876543210mn", "cancelled": true, "credits_refunded": 1 }
Cancel by custom_ref
curl -u user123:mypassword -X DELETE https://api.mobilemessage.com.au/v1/messages \
-H "Content-Type: application/json" \
-d '{"custom_ref":"march-campaign"}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.delete(
'https://api.mobilemessage.com.au/v1/messages',
json={"custom_ref": "march-campaign"},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/messages', {
method: 'DELETE',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ custom_ref: 'march-campaign' })
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/messages');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["custom_ref" => "march-campaign"]));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class CancelByRef {
public static void main(String[] args) throws Exception {
String json = "{\"custom_ref\":\"march-campaign\"}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/messages");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("DELETE");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var request = new HttpRequestMessage(HttpMethod.Delete, "https://api.mobilemessage.com.au/v1/messages") {
Content = new StringContent("{\"custom_ref\":\"march-campaign\"}", Encoding.UTF8, "application/json")
};
var resp = await client.SendAsync(request);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "complete", "custom_ref": "march-campaign", "cancelled_count": 3, "credits_refunded": 3 }
Cancel by send_id
When sending to multiple recipients, the POST response includes a send_id. Use it to cancel all scheduled messages in that send:
curl -u user123:mypassword -X DELETE https://api.mobilemessage.com.au/v1/messages \
-H "Content-Type: application/json" \
-d '{"send_id":42}'
{ "status": "complete", "send_id": 42, "cancelled_count": 150, "credits_refunded": 150 }
Returns HTTP 404 if no matching scheduled message is found. When cancelling by message_id, HTTP 409 is returned if the message exists but is not in a scheduled state. When cancelling by send_id or custom_ref, HTTP 404 is returned when no scheduled messages remain for that reference, including when they have already been sent, and HTTP 409 with "This send is still being processed, please retry shortly." while a large list send is still being prepared. Retry in a moment, once GET /v1/sends reports another status.
Contacts
Manage your contact list. Contacts can be added to lists and used as personalisation sources when sending SMS.
GET /v1/contacts
List contacts with optional filters. Multiple filters are combined with AND. Text fields use partial matching.
| Parameter | Type | Description |
|---|---|---|
number (optional) | String | Exact match by phone number (Australian local or international format). |
first_name (optional) | String | Partial match on first name. |
last_name (optional) | String | Partial match on last name. |
company (optional) | String | Partial match on company. |
other (optional) | String | Partial match on the other/custom field. |
field_1 – field_5 (optional) | String | Partial match on custom fields 1–5. Custom field names are configured in the app under Contacts > Manage Fields. |
limit (optional) | Integer | Results per page. Default 50, max 200. |
offset (optional) | Integer | Pagination offset. Default 0. |
curl -u user123:mypassword "https://api.mobilemessage.com.au/v1/contacts?limit=10"
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
'https://api.mobilemessage.com.au/v1/contacts',
params={'limit': 10},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/contacts?limit=10', {
headers: { 'Authorization': 'Basic ' + btoa('user123:mypassword') }
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/contacts?limit=10');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
echo curl_exec($ch);
curl_close($ch);
?>
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class GetContacts {
public static void main(String[] args) throws Exception {
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/contacts?limit=10");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestProperty("Authorization", "Basic " + credentials);
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var resp = await client.GetAsync("https://api.mobilemessage.com.au/v1/contacts?limit=10");
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{
"status": "complete",
"total": 120,
"limit": 10,
"offset": 0,
"results": [
{ "contact_id": 42, "number": "61412345678", "first_name": "Jane", "last_name": "Smith", "company": "Acme", "other": "", "field_1": "", "field_2": "", "field_3": "", "field_4": "", "field_5": "" }
]
}
POST /v1/contacts
Add a new contact. If duplicate contacts are disabled on your account, adding a number that already exists returns HTTP 409.
| Field | Type | Description |
|---|---|---|
number | String | Phone number (required). |
first_name (optional) | String | First name — used in {first_name} variable substitution. |
last_name (optional) | String | Last name — used in {last_name} substitution. |
company (optional) | String | Company — used in {company} substitution. |
other (optional) | String | Custom field — used in {other} substitution. |
field_1 – field_5 (optional) | String | Custom contact fields 1–5. Used in {field_1}–{field_5} substitution (or custom name slugs configured in the app). |
curl -u user123:mypassword -X POST https://api.mobilemessage.com.au/v1/contacts \
-H "Content-Type: application/json" \
-d '{"number":"0412345678","first_name":"Jane","last_name":"Smith","company":"Acme"}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.post(
'https://api.mobilemessage.com.au/v1/contacts',
json={"number": "0412345678", "first_name": "Jane", "last_name": "Smith", "company": "Acme"},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/contacts', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ number: '0412345678', first_name: 'Jane', last_name: 'Smith', company: 'Acme' })
})
.then(r => r.json())
.then(console.log);
<?php
$data = ["number" => "0412345678", "first_name" => "Jane", "last_name" => "Smith", "company" => "Acme"];
$ch = curl_init('https://api.mobilemessage.com.au/v1/contacts');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class AddContact {
public static void main(String[] args) throws Exception {
String json = "{\"number\":\"0412345678\",\"first_name\":\"Jane\",\"last_name\":\"Smith\",\"company\":\"Acme\"}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/contacts");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("POST");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var json = "{\"number\":\"0412345678\",\"first_name\":\"Jane\",\"last_name\":\"Smith\",\"company\":\"Acme\"}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var resp = await client.PostAsync("https://api.mobilemessage.com.au/v1/contacts", content);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "complete", "contact_id": 42, "number": "61412345678" }
PATCH /v1/contacts
Update one or more fields on an existing contact. Only the fields you provide will be changed.
| Field | Type | Description |
|---|---|---|
contact_id | Integer | ID of the contact to update (required). |
number (optional) | String | New phone number. |
first_name (optional) | String | First name. |
last_name (optional) | String | Last name. |
company (optional) | String | Company. |
other (optional) | String | Custom field. |
field_1 – field_5 (optional) | String | Custom contact fields 1–5. |
curl -u user123:mypassword -X PATCH https://api.mobilemessage.com.au/v1/contacts \
-H "Content-Type: application/json" \
-d '{"contact_id":42,"first_name":"Jane","company":"Acme Corp"}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.patch(
'https://api.mobilemessage.com.au/v1/contacts',
json={"contact_id": 42, "first_name": "Jane", "company": "Acme Corp"},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/contacts', {
method: 'PATCH',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ contact_id: 42, first_name: 'Jane', company: 'Acme Corp' })
})
.then(r => r.json())
.then(console.log);
<?php
$data = ["contact_id" => 42, "first_name" => "Jane", "company" => "Acme Corp"];
$ch = curl_init('https://api.mobilemessage.com.au/v1/contacts');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class EditContact {
public static void main(String[] args) throws Exception {
String json = "{\"contact_id\":42,\"first_name\":\"Jane\",\"company\":\"Acme Corp\"}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/contacts");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("PATCH");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var json = "{\"contact_id\":42,\"first_name\":\"Jane\",\"company\":\"Acme Corp\"}";
var request = new HttpRequestMessage(new HttpMethod("PATCH"), "https://api.mobilemessage.com.au/v1/contacts") {
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
var resp = await client.SendAsync(request);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "complete", "contact_id": 42 }
DELETE /v1/contacts
Remove a contact. The contact is also removed from all lists.
curl -u user123:mypassword -X DELETE https://api.mobilemessage.com.au/v1/contacts \
-H "Content-Type: application/json" \
-d '{"contact_id":42}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.delete(
'https://api.mobilemessage.com.au/v1/contacts',
json={"contact_id": 42},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/contacts', {
method: 'DELETE',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ contact_id: 42 })
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/contacts');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["contact_id" => 42]));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class DeleteContact {
public static void main(String[] args) throws Exception {
String json = "{\"contact_id\":42}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/contacts");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("DELETE");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var request = new HttpRequestMessage(HttpMethod.Delete, "https://api.mobilemessage.com.au/v1/contacts") {
Content = new StringContent("{\"contact_id\":42}", Encoding.UTF8, "application/json")
};
var resp = await client.SendAsync(request);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "complete", "contact_id": 42, "removed": true }
Contact Lists
Organise contacts into named lists for use with Send to List.
GET /v1/lists
List all contact lists including their contact count.
curl -u user123:mypassword https://api.mobilemessage.com.au/v1/lists
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
'https://api.mobilemessage.com.au/v1/lists',
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/lists', {
headers: { 'Authorization': 'Basic ' + btoa('user123:mypassword') }
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/lists');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
echo curl_exec($ch);
curl_close($ch);
?>
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class GetLists {
public static void main(String[] args) throws Exception {
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/lists");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestProperty("Authorization", "Basic " + credentials);
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var resp = await client.GetAsync("https://api.mobilemessage.com.au/v1/lists");
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{
"status": "complete",
"results": [
{ "list_id": 5, "name": "VIP Customers", "created_at": "2026-01-15 09:00:00", "contact_count": 150 }
]
}
POST /v1/lists
Create a contact list. If a list with the same name already exists, the existing list is returned rather than a duplicate being created.
| Field | Type | Description |
|---|---|---|
name | String | The name for the new list (required). |
curl -u user123:mypassword -X POST https://api.mobilemessage.com.au/v1/lists \
-H "Content-Type: application/json" \
-d '{"name":"VIP Customers"}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.post(
'https://api.mobilemessage.com.au/v1/lists',
json={"name": "VIP Customers"},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/lists', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'VIP Customers' })
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/lists');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["name" => "VIP Customers"]));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class CreateList {
public static void main(String[] args) throws Exception {
String json = "{\"name\":\"VIP Customers\"}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/lists");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("POST");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var content = new StringContent("{\"name\":\"VIP Customers\"}", Encoding.UTF8, "application/json");
var resp = await client.PostAsync("https://api.mobilemessage.com.au/v1/lists", content);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "complete", "list_id": 5, "name": "VIP Customers", "existing": false }
existing is true when a list with this name already existed and was returned instead of created.
PATCH /v1/lists
Rename a list. Note: submitting the list's current name unchanged returns HTTP 404 (the update matches no rows) — treat it as a no-op, not a missing list.
| Field | Type | Description |
|---|---|---|
list_id | Integer | ID of the list to rename (required). |
name | String | New name for the list (required). |
curl -u user123:mypassword -X PATCH https://api.mobilemessage.com.au/v1/lists \
-H "Content-Type: application/json" \
-d '{"list_id":5,"name":"VIP Customers 2026"}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.patch(
'https://api.mobilemessage.com.au/v1/lists',
json={"list_id": 5, "name": "VIP Customers 2026"},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/lists', {
method: 'PATCH',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ list_id: 5, name: 'VIP Customers 2026' })
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/lists');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["list_id" => 5, "name" => "VIP Customers 2026"]));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class RenameList {
public static void main(String[] args) throws Exception {
String json = "{\"list_id\":5,\"name\":\"VIP Customers 2026\"}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/lists");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("PATCH");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var json = "{\"list_id\":5,\"name\":\"VIP Customers 2026\"}";
var request = new HttpRequestMessage(new HttpMethod("PATCH"), "https://api.mobilemessage.com.au/v1/lists") {
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
var resp = await client.SendAsync(request);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "complete", "list_id": 5, "name": "VIP Customers 2026" }
DELETE /v1/lists
Delete a list. By default only the list and its membership records are removed — contact records are kept. Set delete_contacts to true to also permanently delete all contact records that were members of the list.
| Field | Type | Description |
|---|---|---|
list_id | Integer | ID of the list to delete (required). |
delete_contacts (optional) | Boolean | If true, also permanently delete all contact records that were members of this list. Defaults to false. |
# Delete list only (keep contacts)
curl -u user123:mypassword -X DELETE https://api.mobilemessage.com.au/v1/lists \
-H "Content-Type: application/json" \
-d '{"list_id":5}'
# Delete list and all its contacts
curl -u user123:mypassword -X DELETE https://api.mobilemessage.com.au/v1/lists \
-H "Content-Type: application/json" \
-d '{"list_id":5,"delete_contacts":true}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.delete(
'https://api.mobilemessage.com.au/v1/lists',
json={"list_id": 5}, # add "delete_contacts": True to also delete contacts
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/lists', {
method: 'DELETE',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ list_id: 5 }) // add delete_contacts: true to also delete contacts
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/lists');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["list_id" => 5]));
// Add "delete_contacts" => true to also delete contacts
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class DeleteList {
public static void main(String[] args) throws Exception {
String json = "{\"list_id\":5}"; // add ,"delete_contacts":true to also delete contacts
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/lists");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("DELETE");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
// Add \"delete_contacts\":true to also delete contacts
var request = new HttpRequestMessage(HttpMethod.Delete, "https://api.mobilemessage.com.au/v1/lists") {
Content = new StringContent("{\"list_id\":5}", Encoding.UTF8, "application/json")
};
var resp = await client.SendAsync(request);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "complete", "list_id": 5, "removed": true }
List Members
Add, remove, and view contacts within a specific list.
GET /v1/list-contacts
| Parameter | Type | Description |
|---|---|---|
list_id | Integer | ID of the list to query (required). |
limit (optional) | Integer | Default 50, max 200. |
offset (optional) | Integer | Pagination offset. |
curl -u user123:mypassword "https://api.mobilemessage.com.au/v1/list-contacts?list_id=5"
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
'https://api.mobilemessage.com.au/v1/list-contacts',
params={'list_id': 5},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/list-contacts?list_id=5', {
headers: { 'Authorization': 'Basic ' + btoa('user123:mypassword') }
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/list-contacts?list_id=5');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
echo curl_exec($ch);
curl_close($ch);
?>
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class GetListMembers {
public static void main(String[] args) throws Exception {
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/list-contacts?list_id=5");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestProperty("Authorization", "Basic " + credentials);
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var resp = await client.GetAsync("https://api.mobilemessage.com.au/v1/list-contacts?list_id=5");
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{
"status": "complete",
"list_id": 5,
"total": 1,
"limit": 50,
"offset": 0,
"results": [
{ "contact_id": 42, "number": "61412345678", "first_name": "Jane", "last_name": "Smith", "company": "Acme", "other": "", "field_1": "", "field_2": "", "field_3": "", "field_4": "", "field_5": "", "added": "2026-01-20 10:00:00" }
]
}
POST /v1/list-contacts
Add a contact to a list. Has no effect if the contact is already a member.
| Field | Type | Description |
|---|---|---|
list_id | Integer | ID of the list (required). |
contact_id (optional*) | Integer | ID of the contact to add. One of contact_id or number is required. |
number (optional*) | String | Australian mobile number of the contact to add. One of contact_id or number is required. |
curl -u user123:mypassword -X POST https://api.mobilemessage.com.au/v1/list-contacts \
-H "Content-Type: application/json" \
-d '{"list_id":5,"contact_id":42}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.post(
'https://api.mobilemessage.com.au/v1/list-contacts',
json={"list_id": 5, "contact_id": 42},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/list-contacts', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ list_id: 5, contact_id: 42 })
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/list-contacts');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["list_id" => 5, "contact_id" => 42]));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class AddListMember {
public static void main(String[] args) throws Exception {
String json = "{\"list_id\":5,\"contact_id\":42}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/list-contacts");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("POST");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var content = new StringContent("{\"list_id\":5,\"contact_id\":42}", Encoding.UTF8, "application/json");
var resp = await client.PostAsync("https://api.mobilemessage.com.au/v1/list-contacts", content);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "complete", "list_id": 5, "contact_id": 42, "added": true }
DELETE /v1/list-contacts
Remove a contact from a list (the contact itself is not deleted).
| Field | Type | Description |
|---|---|---|
list_id | Integer | ID of the list (required). |
contact_id (optional*) | Integer | ID of the contact to remove. One of contact_id or number is required. |
number (optional*) | String | Australian mobile number of the contact to remove. One of contact_id or number is required. |
curl -u user123:mypassword -X DELETE https://api.mobilemessage.com.au/v1/list-contacts \
-H "Content-Type: application/json" \
-d '{"list_id":5,"contact_id":42}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.delete(
'https://api.mobilemessage.com.au/v1/list-contacts',
json={"list_id": 5, "contact_id": 42},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/list-contacts', {
method: 'DELETE',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ list_id: 5, contact_id: 42 })
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/list-contacts');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["list_id" => 5, "contact_id" => 42]));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class RemoveListMember {
public static void main(String[] args) throws Exception {
String json = "{\"list_id\":5,\"contact_id\":42}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/list-contacts");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("DELETE");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var request = new HttpRequestMessage(HttpMethod.Delete, "https://api.mobilemessage.com.au/v1/list-contacts") {
Content = new StringContent("{\"list_id\":5,\"contact_id\":42}", Encoding.UTF8, "application/json")
};
var resp = await client.SendAsync(request);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "complete", "list_id": 5, "contact_id": 42, "removed": true }
Unsubscribes
Manage your opt-out list. Adding a number blocks it from receiving future messages, cancels any scheduled messages to that number, and removes the contact from all lists.
GET /v1/unsubscribes
| Parameter | Type | Description |
|---|---|---|
number (optional) | String | Filter by phone number. |
limit (optional) | Integer | Results per page. Default 50, max 200. |
offset (optional) | Integer | Pagination offset. Default 0. |
curl -u user123:mypassword "https://api.mobilemessage.com.au/v1/unsubscribes?limit=100&offset=0"
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
'https://api.mobilemessage.com.au/v1/unsubscribes',
params={'limit': 100, 'offset': 0},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/unsubscribes?limit=100&offset=0', {
headers: { 'Authorization': 'Basic ' + btoa('user123:mypassword') }
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/unsubscribes?limit=100&offset=0');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
echo curl_exec($ch);
curl_close($ch);
?>
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class GetUnsubscribes {
public static void main(String[] args) throws Exception {
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/unsubscribes?limit=100&offset=0");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestProperty("Authorization", "Basic " + credentials);
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var resp = await client.GetAsync("https://api.mobilemessage.com.au/v1/unsubscribes?limit=100&offset=0");
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{
"status": "complete",
"total": 1250,
"limit": 100,
"offset": 0,
"results": [
{ "number": "61412345678", "updated_at": "2026-01-15 09:00:00" }
]
}
POST /v1/unsubscribes
Add a number to your unsubscribe list.
curl -u user123:mypassword -X POST https://api.mobilemessage.com.au/v1/unsubscribes \
-H "Content-Type: application/json" \
-d '{"number":"0412345678"}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.post(
'https://api.mobilemessage.com.au/v1/unsubscribes',
json={"number": "0412345678"},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/unsubscribes', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ number: '0412345678' })
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/unsubscribes');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["number" => "0412345678"]));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class AddUnsubscribe {
public static void main(String[] args) throws Exception {
String json = "{\"number\":\"0412345678\"}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/unsubscribes");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("POST");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var content = new StringContent("{\"number\":\"0412345678\"}", Encoding.UTF8, "application/json");
var resp = await client.PostAsync("https://api.mobilemessage.com.au/v1/unsubscribes", content);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "complete", "number": "61412345678", "added": true }
DELETE /v1/unsubscribes
Remove a number from your unsubscribe list.
curl -u user123:mypassword -X DELETE https://api.mobilemessage.com.au/v1/unsubscribes \
-H "Content-Type: application/json" \
-d '{"number":"0412345678"}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.delete(
'https://api.mobilemessage.com.au/v1/unsubscribes',
json={"number": "0412345678"},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/unsubscribes', {
method: 'DELETE',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ number: '0412345678' })
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/unsubscribes');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["number" => "0412345678"]));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class DeleteUnsubscribe {
public static void main(String[] args) throws Exception {
String json = "{\"number\":\"0412345678\"}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/unsubscribes");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("DELETE");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var request = new HttpRequestMessage(HttpMethod.Delete, "https://api.mobilemessage.com.au/v1/unsubscribes") {
Content = new StringContent("{\"number\":\"0412345678\"}", Encoding.UTF8, "application/json")
};
var resp = await client.SendAsync(request);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "complete", "number": "61412345678", "removed": true }
Account Credit Balance
GET /v1/account
Retrieve your current SMS credit balance using your authenticated credentials. No extra parameters are required.
Code Examples
# Execute the GET request and view the response
curl -u user123:mypassword -X GET https://api.mobilemessage.com.au/v1/account
# Example response output:
# {"status": "complete", "credit_balance": 1000}
import requests
from requests.auth import HTTPBasicAuth
url = "https://api.mobilemessage.com.au/v1/account"
response = requests.get(url, auth=HTTPBasicAuth('user123', 'mypassword'))
# Parse the JSON response
data = response.json()
credit_balance = data.get('credit_balance', 'N/A')
print(f"Credit Balance: {credit_balance}")
# Output might be: Credit Balance: 1000
// Using Fetch API to get and process account balance
fetch('https://api.mobilemessage.com.au/v1/account', {
method: 'GET',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
const creditBalance = data.credit_balance || 'N/A';
console.log("Credit Balance:", creditBalance);
})
.catch(error => console.error(error));
<?php
$url = "https://api.mobilemessage.com.au/v1/account";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
$creditBalance = isset($data['credit_balance']) ? $data['credit_balance'] : 'N/A';
echo "Credit Balance: " . $creditBalance;
// Output might be: Credit Balance: 1000
?>
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
import org.json.JSONObject; // Requires org.json library
public class GetBalance {
public static void main(String[] args) throws Exception {
URL url = new URL("https://api.mobilemessage.com.au/v1/account");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
connection.setRequestProperty("Authorization", "Basic " + credentials);
connection.setRequestMethod("GET");
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuilder responseStr = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
responseStr.append(inputLine);
}
in.close();
JSONObject json = new JSONObject(responseStr.toString());
int creditBalance = json.getInt("credit_balance");
System.out.println("Credit Balance: " + creditBalance);
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq; // Requires Newtonsoft.Json package
class Program {
static async Task Main() {
var client = new HttpClient();
var byteArray = Encoding.ASCII.GetBytes("user123:mypassword");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
var response = await client.GetAsync("https://api.mobilemessage.com.au/v1/account");
var responseString = await response.Content.ReadAsStringAsync();
var json = JObject.Parse(responseString);
var creditBalance = json["credit_balance"];
Console.WriteLine("Credit Balance: " + creditBalance);
}
}
Example Successful Response (200)
{
"status": "complete",
"credit_balance": 1000,
"credit_price": 0.049
}
credit_price is your effective price per credit in AUD, excluding GST, taken from your most recent credit purchase — useful for converting credit usage into dollar spend in your own reporting. It is null if the account has not purchased credits yet.
Example Error Responses
Account Not Found (404)
{
"error": "Account not found or no credit balance available."
}
Invalid Request Method (405)
{
"error": "Invalid request method. Only GET is allowed."
}
Sender IDs
List your active Sender IDs or register your own mobile number as a sender via a two-step verification flow.
GET /v1/senders
List all active Sender IDs on your account.
curl -u user123:mypassword https://api.mobilemessage.com.au/v1/senders
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
'https://api.mobilemessage.com.au/v1/senders',
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/senders', {
headers: { 'Authorization': 'Basic ' + btoa('user123:mypassword') }
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/senders');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
echo curl_exec($ch);
curl_close($ch);
?>
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class GetSenders {
public static void main(String[] args) throws Exception {
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/senders");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestProperty("Authorization", "Basic " + credentials);
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var resp = await client.GetAsync("https://api.mobilemessage.com.au/v1/senders");
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{
"status": "complete",
"results": [
{ "sender": "CompanyABC", "type": "alpha", "label": "My Brand", "is_default": true },
{ "sender": "61412345678", "type": "own", "label": "My mobile", "is_default": false }
]
}
POST /v1/senders
Register your own mobile number as a sender. An SMS containing a confirmation link is sent to the number at no charge. The owner must click the link and confirm before the number is activated as a sender on your account.
| Field | Type | Description |
|---|---|---|
number | String | The mobile number to register. |
label (optional) | String | A label to identify this sender in your account. |
curl -u user123:mypassword -X POST https://api.mobilemessage.com.au/v1/senders \
-H "Content-Type: application/json" \
-d '{"number":"0412345678","label":"My mobile"}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.post(
'https://api.mobilemessage.com.au/v1/senders',
json={"number": "0412345678", "label": "My mobile"},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/senders', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ number: '0412345678', label: 'My mobile' })
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/senders');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["number" => "0412345678", "label" => "My mobile"]));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class AddSender {
public static void main(String[] args) throws Exception {
String json = "{\"number\":\"0412345678\",\"label\":\"My mobile\"}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/senders");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("POST");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var content = new StringContent("{\"number\":\"0412345678\",\"label\":\"My mobile\"}", Encoding.UTF8, "application/json");
var resp = await client.PostAsync("https://api.mobilemessage.com.au/v1/senders", content);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "verification_sent", "message": "A verification link has been sent to the number." }
Returns HTTP 409 if the number is already an active sender on your account. Returns HTTP 429 if you have too many pending verifications or a verification was sent to that number in the last 5 minutes.
ACMA Registration
Register a custom alphanumeric Sender ID (e.g. "MyBrand") with the Australian Communications and Media Authority (ACMA). All custom Sender IDs in Australia must be registered through the ACMA Sender ID Register. After submission, registrations are reviewed by Mobile Message before being forwarded to ACMA for approval.
GET /v1/acma-registration
List your ACMA sender ID registrations along with your saved brands and partner organisations. If you include the sender query parameter, the ACMA status of that specific Sender ID is returned instead.
Query parameters
| Parameter | Type | Description |
|---|---|---|
sender (optional) | String | If provided, returns the ACMA registration status for this Sender ID instead of the full listing. |
List brands and partners
curl -u user123:mypassword https://api.mobilemessage.com.au/v1/acma-registration
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
'https://api.mobilemessage.com.au/v1/acma-registration',
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/acma-registration', {
headers: { 'Authorization': 'Basic ' + btoa('user123:mypassword') }
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/acma-registration');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
echo curl_exec($ch);
curl_close($ch);
?>
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class GetAcma {
public static void main(String[] args) throws Exception {
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/acma-registration");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestProperty("Authorization", "Basic " + credentials);
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var resp = await client.GetAsync("https://api.mobilemessage.com.au/v1/acma-registration");
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{
"registrations": [
{ "sender_id": 3, "sender": "MyBrand", "status": "active", "acma_status": "verified", "brand_id": 1, "partner_id": null },
{ "sender_id": 15, "sender": "NewBrand", "status": "pending", "acma_status": "pending_internal_review", "brand_id": 1, "partner_id": 5 }
],
"brands": [
{ "id": 1, "brand_name": "Example Pty Ltd", "abn": "12345678901", "website": "https://example.com.au" }
],
"partners": [
{ "id": 5, "partner_name": "Partner Agency", "abn": "98765432109", "website": "https://partner.com.au" }
]
}
Response fields
The registrations array contains all custom (alphanumeric) Sender IDs on your account. Each registration has the following fields:
| Field | Description |
|---|---|
sender_id | The internal ID for this sender. Use this as the sender_id field in a POST request if you need to re-register a previously rejected sender. |
sender | The alphanumeric Sender ID text (e.g. "MyBrand"). |
status | The sender's overall status: active (approved and ready to use for sending) or pending (registration in progress, not yet available for sending). |
acma_status |
The current stage of the ACMA registration process:
|
brand_id | The ID of the brand associated with this registration, or null if not yet linked. Corresponds to an entry in the brands array. |
partner_id | The ID of the on-behalf-of partner associated with this registration, or null if not applicable. Corresponds to an entry in the partners array. |
The brands array contains your saved organisations. Pass the id as brand_id with brand_mode set to existing when submitting a registration to reuse a brand without re-entering its details.
The partners array contains your saved on-behalf-of organisations. Pass the id as partner_id when submitting a registration on behalf of a previously used client.
Check sender ACMA status
curl -u user123:mypassword "https://api.mobilemessage.com.au/v1/acma-registration?sender=MyBrand"
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
'https://api.mobilemessage.com.au/v1/acma-registration',
params={'sender': 'MyBrand'},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/acma-registration?sender=MyBrand', {
headers: { 'Authorization': 'Basic ' + btoa('user123:mypassword') }
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/acma-registration?sender=MyBrand');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
echo curl_exec($ch);
curl_close($ch);
?>
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class GetAcmaStatus {
public static void main(String[] args) throws Exception {
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/acma-registration?sender=MyBrand");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestProperty("Authorization", "Basic " + credentials);
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var resp = await client.GetAsync("https://api.mobilemessage.com.au/v1/acma-registration?sender=MyBrand");
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{
"sender": "MyBrand",
"sender_id": 15,
"acma_status": "pending_internal_review",
"brand_id": 1,
"chatbot_id": 8,
"partner_id": null
}
Returns HTTP 404 if the sender is not found on your account.
POST /v1/acma-registration
Submit a new custom Sender ID for ACMA registration. The registration will be reviewed by Mobile Message before being forwarded to ACMA. You can reuse previously saved brands and partners by passing their id from the GET response.
Sender ID fields
| Field | Type | Description |
|---|---|---|
sender | String | The alphanumeric Sender ID to register. 1–11 characters, printable ASCII only, cannot be only numbers. |
sender_id (optional) | Integer | An existing sender_id from your account to re-register (e.g. after a previous rejection). If omitted, a new sender record is created. |
sender_id_relation_type | String | How the Sender ID relates to the organisation. One of:
|
sender_id_relation_detail | String | Required unless sender_id_relation_type is registered_company_name. The value depends on the relation type:
|
Contact fields (always required)
The contact person at the brand/organisation that the Sender ID belongs to. When registering on behalf of a client, these should be the client's contact details, not yours (your details go in the on-behalf-of fields below).
| Field | Type | Description |
|---|---|---|
contact_first_name | String | Contact first name at the brand/organisation. |
contact_last_name | String | Contact last name at the brand/organisation. |
contact_email | String | Contact email address at the brand/organisation. |
Brand / organisation fields
You can either reuse an existing brand or provide details for a new one.
| Field | Type | Description |
|---|---|---|
brand_mode | String | new (default) or existing. Set to existing to reuse a previously saved brand by its brand_id. |
brand_id | Integer | Required when brand_mode is existing. The brand ID from GET /v1/acma-registration. |
abn | String | Australian Business Number (11 digits, spaces allowed). Required when brand_mode is new. |
business_name | String | Registered business name. Required when brand_mode is new. |
address_line1 | String | Business street address. Required when brand_mode is new. |
address_line2 (optional) | String | Additional address line. |
suburb | String | Suburb or city. Required when brand_mode is new. |
state | String | Australian state or territory: ACT, NSW, NT, QLD, SA, TAS, VIC, or WA. Required when brand_mode is new. |
postcode | String | Postcode. Required when brand_mode is new. |
website | String | Business website URL. Required when brand_mode is new. |
business_phone | String | Business phone number. Required when brand_mode is new. |
On-behalf-of fields (registering on behalf of a client)
Set on_behalf to true when you are registering a Sender ID for a business that is not your own. This is common for marketing agencies, IT service providers, or any business that sends SMS on behalf of their clients.
When registering on behalf of a client, the Brand / organisation fields above should contain the details of the client — the business whose name will appear as the Sender ID. The on-behalf-of fields below should contain your business details as the applicant performing the registration.
For example, if your marketing agency "Acme Marketing" is registering the Sender ID "PizzaCo" for your client "Pizza Company Pty Ltd", the brand fields would contain Pizza Company's ABN, address and contact details, and the on-behalf-of fields would contain Acme Marketing's details.
You can reuse a previously saved partner by passing partner_id from the GET response instead of providing all applicant fields again.
| Field | Type | Description |
|---|---|---|
on_behalf | Boolean | Set to true if registering on behalf of another business. Defaults to false. |
partner_id (optional) | Integer | Reuse an existing partner by ID from GET /v1/acma-registration. If provided, the applicant fields below are not required. |
applicant_abn | String | Applicant's ABN (11 digits). Required when on_behalf is true and no partner_id. |
applicant_name | String | Applicant's organisation name. Required when on_behalf is true and no partner_id. |
applicant_contact_first_name | String | Applicant contact first name. Required when on_behalf is true and no partner_id. |
applicant_contact_last_name | String | Applicant contact last name. Required when on_behalf is true and no partner_id. |
applicant_contact_email | String | Applicant contact email. Required when on_behalf is true and no partner_id. |
applicant_website | String | Applicant's website URL. Required when on_behalf is true and no partner_id. |
applicant_phone (optional) | String | Applicant's business phone number. |
applicant_address_line1 | String | Applicant's street address. Required when on_behalf is true and no partner_id. |
applicant_address_line2 (optional) | String | Additional address line. |
applicant_suburb | String | Applicant's suburb or city. Required when on_behalf is true and no partner_id. |
applicant_state | String | Applicant's state or territory. Required when on_behalf is true and no partner_id. |
applicant_postcode | String | Applicant's postcode. Required when on_behalf is true and no partner_id. |
Example: register with a new brand
curl -u user123:mypassword -X POST https://api.mobilemessage.com.au/v1/acma-registration \
-H "Content-Type: application/json" \
-d '{
"sender": "MyBrand",
"sender_id_relation_type": "registered_company_name",
"contact_first_name": "Jane",
"contact_last_name": "Smith",
"contact_email": "jane@example.com.au",
"abn": "12345678901",
"business_name": "Example Pty Ltd",
"address_line1": "123 Collins St",
"suburb": "Melbourne",
"state": "VIC",
"postcode": "3000",
"website": "https://example.com.au",
"business_phone": "0398765432",
}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.post(
'https://api.mobilemessage.com.au/v1/acma-registration',
json={
'sender': 'MyBrand',
'sender_id_relation_type': 'registered_company_name',
'contact_first_name': 'Jane',
'contact_last_name': 'Smith',
'contact_email': 'jane@example.com.au',
'abn': '12345678901',
'business_name': 'Example Pty Ltd',
'address_line1': '123 Collins St',
'suburb': 'Melbourne',
'state': 'VIC',
'postcode': '3000',
'website': 'https://example.com.au',
'business_phone': '0398765432',
},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/acma-registration', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
sender: 'MyBrand',
sender_id_relation_type: 'registered_company_name',
contact_first_name: 'Jane',
contact_last_name: 'Smith',
contact_email: 'jane@example.com.au',
abn: '12345678901',
business_name: 'Example Pty Ltd',
address_line1: '123 Collins St',
suburb: 'Melbourne',
state: 'VIC',
postcode: '3000',
website: 'https://example.com.au',
business_phone: '0398765432',
})
})
.then(r => r.json())
.then(console.log);
<?php
$data = [
'sender' => 'MyBrand',
'sender_id_relation_type' => 'registered_company_name',
'contact_first_name' => 'Jane',
'contact_last_name' => 'Smith',
'contact_email' => 'jane@example.com.au',
'abn' => '12345678901',
'business_name' => 'Example Pty Ltd',
'address_line1' => '123 Collins St',
'suburb' => 'Melbourne',
'state' => 'VIC',
'postcode' => '3000',
'website' => 'https://example.com.au',
'business_phone' => '0398765432',
];
$ch = curl_init('https://api.mobilemessage.com.au/v1/acma-registration');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class AcmaRegister {
public static void main(String[] args) throws Exception {
String json = """
{
"sender": "MyBrand",
"sender_id_relation_type": "registered_company_name",
"contact_first_name": "Jane",
"contact_last_name": "Smith",
"contact_email": "jane@example.com.au",
"abn": "12345678901",
"business_name": "Example Pty Ltd",
"address_line1": "123 Collins St",
"suburb": "Melbourne",
"state": "VIC",
"postcode": "3000",
"website": "https://example.com.au",
"business_phone": "0398765432",
}""";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/acma-registration");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("POST");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var json = @"{
""sender"": ""MyBrand"",
""sender_id_relation_type"": ""registered_company_name"",
""contact_first_name"": ""Jane"",
""contact_last_name"": ""Smith"",
""contact_email"": ""jane@example.com.au"",
""abn"": ""12345678901"",
""business_name"": ""Example Pty Ltd"",
""address_line1"": ""123 Collins St"",
""suburb"": ""Melbourne"",
""state"": ""VIC"",
""postcode"": ""3000"",
""website"": ""https://example.com.au"",
""business_phone"": ""0398765432"",
}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var resp = await client.PostAsync("https://api.mobilemessage.com.au/v1/acma-registration", content);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{
"status": "complete",
"message": "Sender ID registration submitted and is pending review.",
"sender_id": 15,
"brand_id": 1,
"chatbot_id": 8,
"partner_id": null,
"acma_status": "pending_internal_review"
}
Example: register with an existing brand
curl -u user123:mypassword -X POST https://api.mobilemessage.com.au/v1/acma-registration \
-H "Content-Type: application/json" \
-d '{
"sender": "ExBrand",
"brand_mode": "existing",
"brand_id": 1,
"sender_id_relation_type": "registered_domain_name",
"sender_id_relation_detail": "example.com.au",
"contact_first_name": "Jane",
"contact_last_name": "Smith",
"contact_email": "jane@example.com.au",
}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.post(
'https://api.mobilemessage.com.au/v1/acma-registration',
json={
'sender': 'ExBrand',
'brand_mode': 'existing',
'brand_id': 1,
'sender_id_relation_type': 'registered_domain_name',
'sender_id_relation_detail': 'example.com.au',
'contact_first_name': 'Jane',
'contact_last_name': 'Smith',
'contact_email': 'jane@example.com.au',
},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/acma-registration', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
sender: 'ExBrand',
brand_mode: 'existing',
brand_id: 1,
sender_id_relation_type: 'registered_domain_name',
sender_id_relation_detail: 'example.com.au',
contact_first_name: 'Jane',
contact_last_name: 'Smith',
contact_email: 'jane@example.com.au',
})
})
.then(r => r.json())
.then(console.log);
<?php
$data = [
'sender' => 'ExBrand',
'brand_mode' => 'existing',
'brand_id' => 1,
'sender_id_relation_type' => 'registered_domain_name',
'sender_id_relation_detail' => 'example.com.au',
'contact_first_name' => 'Jane',
'contact_last_name' => 'Smith',
'contact_email' => 'jane@example.com.au',
];
$ch = curl_init('https://api.mobilemessage.com.au/v1/acma-registration');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class AcmaExistingBrand {
public static void main(String[] args) throws Exception {
String json = """
{
"sender": "ExBrand",
"brand_mode": "existing",
"brand_id": 1,
"sender_id_relation_type": "registered_domain_name",
"sender_id_relation_detail": "example.com.au",
"contact_first_name": "Jane",
"contact_last_name": "Smith",
"contact_email": "jane@example.com.au",
}""";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/acma-registration");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("POST");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var json = @"{
""sender"": ""ExBrand"",
""brand_mode"": ""existing"",
""brand_id"": 1,
""sender_id_relation_type"": ""registered_domain_name"",
""sender_id_relation_detail"": ""example.com.au"",
""contact_first_name"": ""Jane"",
""contact_last_name"": ""Smith"",
""contact_email"": ""jane@example.com.au"",
}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var resp = await client.PostAsync("https://api.mobilemessage.com.au/v1/acma-registration", content);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
Example: register on behalf of another business
curl -u user123:mypassword -X POST https://api.mobilemessage.com.au/v1/acma-registration \
-H "Content-Type: application/json" \
-d '{
"sender": "ClientCo",
"brand_mode": "existing",
"brand_id": 1,
"sender_id_relation_type": "registered_business_name",
"sender_id_relation_detail": "Client Company",
"contact_first_name": "Jane",
"contact_last_name": "Smith",
"contact_email": "jane@example.com.au",
"on_behalf": true,
"applicant_abn": "98765432109",
"applicant_name": "Client Company Pty Ltd",
"applicant_contact_first_name": "Bob",
"applicant_contact_last_name": "Jones",
"applicant_contact_email": "bob@clientcompany.com.au",
"applicant_website": "https://clientcompany.com.au",
"applicant_address_line1": "10 George St",
"applicant_suburb": "Sydney",
"applicant_state": "NSW",
"applicant_postcode": "2000",
}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.post(
'https://api.mobilemessage.com.au/v1/acma-registration',
json={
'sender': 'ClientCo',
'brand_mode': 'existing',
'brand_id': 1,
'sender_id_relation_type': 'registered_business_name',
'sender_id_relation_detail': 'Client Company',
'contact_first_name': 'Jane',
'contact_last_name': 'Smith',
'contact_email': 'jane@example.com.au',
'on_behalf': True,
'applicant_abn': '98765432109',
'applicant_name': 'Client Company Pty Ltd',
'applicant_contact_first_name': 'Bob',
'applicant_contact_last_name': 'Jones',
'applicant_contact_email': 'bob@clientcompany.com.au',
'applicant_website': 'https://clientcompany.com.au',
'applicant_address_line1': '10 George St',
'applicant_suburb': 'Sydney',
'applicant_state': 'NSW',
'applicant_postcode': '2000',
},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/acma-registration', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({
sender: 'ClientCo',
brand_mode: 'existing',
brand_id: 1,
sender_id_relation_type: 'registered_business_name',
sender_id_relation_detail: 'Client Company',
contact_first_name: 'Jane',
contact_last_name: 'Smith',
contact_email: 'jane@example.com.au',
on_behalf: true,
applicant_abn: '98765432109',
applicant_name: 'Client Company Pty Ltd',
applicant_contact_first_name: 'Bob',
applicant_contact_last_name: 'Jones',
applicant_contact_email: 'bob@clientcompany.com.au',
applicant_website: 'https://clientcompany.com.au',
applicant_address_line1: '10 George St',
applicant_suburb: 'Sydney',
applicant_state: 'NSW',
applicant_postcode: '2000',
})
})
.then(r => r.json())
.then(console.log);
<?php
$data = [
'sender' => 'ClientCo',
'brand_mode' => 'existing',
'brand_id' => 1,
'sender_id_relation_type' => 'registered_business_name',
'sender_id_relation_detail' => 'Client Company',
'contact_first_name' => 'Jane',
'contact_last_name' => 'Smith',
'contact_email' => 'jane@example.com.au',
'on_behalf' => true,
'applicant_abn' => '98765432109',
'applicant_name' => 'Client Company Pty Ltd',
'applicant_contact_first_name' => 'Bob',
'applicant_contact_last_name' => 'Jones',
'applicant_contact_email' => 'bob@clientcompany.com.au',
'applicant_website' => 'https://clientcompany.com.au',
'applicant_address_line1' => '10 George St',
'applicant_suburb' => 'Sydney',
'applicant_state' => 'NSW',
'applicant_postcode' => '2000',
];
$ch = curl_init('https://api.mobilemessage.com.au/v1/acma-registration');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
echo curl_exec($ch);
curl_close($ch);
?>
{
"status": "complete",
"message": "Sender ID registration submitted and is pending review.",
"sender_id": 16,
"brand_id": 1,
"chatbot_id": 9,
"partner_id": 5,
"acma_status": "pending_internal_review"
}
GET /v1/acma-registration to list your saved brands and partners and pass their id directly via brand_id or partner_id.
Error responses
| HTTP Status | Meaning |
|---|---|
400 | Validation error — a required field is missing or invalid. The error field describes the issue. |
404 | The specified brand_id, partner_id, or sender_id was not found on your account. |
409 | This Sender ID is already registered or has a pending ACMA submission. Contact support to make changes. |
Purchases
An API key can buy SMS credits and dedicated numbers, charged to a card saved on your account. Purchases are off for every key until you turn them on.
Turning purchases on
In your dashboard, go to Settings > API and choose Purchases on the key. Each key has its own setting and its own daily spending limit. Only users who can manage billing can turn purchases on or raise a limit. The account owner is emailed when purchases are turned on for a key or a limit is raised. When a purchase goes through, its tax invoice goes to the billing notification email on the account and names the key. If no billing email is set, or the payment is held for review, the account owner is emailed about the purchase instead.
Which card is charged
Cards are saved under Settings > Billing. If your account has one saved card, purchases use it. If it has more than one, include card_last4 with the last 4 digits of the card to charge. A request that needs card_last4 and leaves it out is answered with the list of saved cards (brand, last 4 digits and expiry) so you can choose one.
Daily spending limit
Each key has a daily spending limit in AUD, including GST. It starts at $500 and can be changed on the same screen. A purchase that would take the key's spending over the limit in any 24 hours is declined with daily_cap_exceeded, and the response includes how much is left.
Safe retries with an Idempotency-Key
Send an Idempotency-Key header with a new unique value, such as a UUID, for each purchase. If a request times out or the connection drops, send it again with the same key and the same body. You get the result of the original purchase and the card is not charged a second time. Without the header, every request is treated as a new purchase. Keys are kept for 24 hours.
Payment reviews
Some payments are reviewed before credits are added. While any payment on your account is being reviewed, purchases through the API are paused and return payment_in_review.
Purchase errors
Purchase errors include a code alongside the usual error message, so your integration can handle each case.
{ "error": "This account has more than one saved card. Add \"card_last4\" with the last 4 digits of the card to charge.", "code": "card_last4_required", "cards": [ { "brand": "visa", "last4": "4242", "exp_month": 8, "exp_year": 2027 } ] }
| Code | HTTP | Meaning |
|---|---|---|
purchases_disabled | 403 | Purchases are turned off for this API key. |
test_account | 403 | Purchases are not available on test accounts. |
payment_in_review | 409 | A payment on the account is being reviewed. Try again once the review is finished. |
no_saved_card | 402 | The account has no saved card. |
card_last4_required | 400 | The account has more than one saved card. The response lists them in cards. |
card_not_found | 400 | No saved card ends in the given card_last4. |
card_ambiguous | 409 | More than one saved card ends in the given card_last4. |
invalid_card_last4 | 400 | card_last4 is not 4 digits. |
daily_cap_exceeded | 402 | The purchase would take the key over its daily spending limit. Includes daily_cap and remaining. |
card_declined | 402 | The card issuer declined the payment. May include decline_code. |
authentication_required | 402 | The card issuer wants the cardholder to approve this payment. Make the purchase in your dashboard instead. |
payment_incomplete | 402 | The payment could not be completed without the cardholder. Make the purchase in your dashboard instead. |
purchase_in_progress | 409 | Another purchase on the account is being processed. Try again in a moment. |
previous_purchase_processing | 409 | An earlier credit purchase on the account is still being recorded. Try again in a moment. |
idempotency_conflict | 409 | The Idempotency-Key was already used for a different purchase. |
payment_error | 502 | Our payment provider could not be reached. Check your balance or numbers before trying again, or retry with the same Idempotency-Key. |
purchases_unavailable | 503 | Purchases are temporarily unavailable. |
Reusing an Idempotency-Key with a different request body returns HTTP 422.
Buy SMS Credits
POST /v1/credit-purchases
Buy SMS credits with your saved card. Credits are priced at your account's current rate, which is the same price you see when adding credits in your dashboard.
| Field | Type | Description |
|---|---|---|
credits | Integer | The number of credits to buy. The minimum is 500, or your account's minimum if it has custom pricing. |
card_last4 (optional) | String | Last 4 digits of the saved card to charge. Required when the account has more than one saved card. |
Code Examples
curl -u user123:mypassword -X POST https://api.mobilemessage.com.au/v1/credit-purchases \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5f0c6b2e-8d1a-4c3e-9a57-2b1f0e7d4c11" \
-d '{"credits":10000}'
import uuid
import requests
from requests.auth import HTTPBasicAuth
response = requests.post(
'https://api.mobilemessage.com.au/v1/credit-purchases',
json={"credits": 10000},
headers={'Idempotency-Key': str(uuid.uuid4())},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.status_code, response.json())
fetch('https://api.mobilemessage.com.au/v1/credit-purchases', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID()
},
body: JSON.stringify({ credits: 10000 })
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/credit-purchases');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16))
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["credits" => 10000]));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
import java.util.UUID;
public class BuyCredits {
public static void main(String[] args) throws Exception {
String json = "{\"credits\":10000}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/credit-purchases");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("POST");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setRequestProperty("Idempotency-Key", UUID.randomUUID().toString());
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
client.DefaultRequestHeaders.Add("Idempotency-Key", Guid.NewGuid().ToString());
var content = new StringContent("{\"credits\":10000}", Encoding.UTF8, "application/json");
var resp = await client.PostAsync("https://api.mobilemessage.com.au/v1/credit-purchases", content);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{
"status": "credited",
"credits": 10000,
"amount": "330.00",
"currency": "AUD",
"card_last4": "4242",
"payment_intent_id": "pi_3Q...",
"credit_balance": 12450
}
| status | HTTP | Meaning |
|---|---|---|
credited | 201 | The credits were added. credit_balance is your new balance. |
pending_review | 202 | The payment went through and is being reviewed. The credits are added when the review is finished, and the account owner is emailed. |
processing | 202 | The payment went through and the credits are being added. Check GET /v1/account for the new balance, or retry with the same Idempotency-Key for the final status. |
not_credited | 200 | Only returned for a retry of an earlier purchase that was reviewed and not approved. No credits were added. |
Returns HTTP 400 with the code invalid_credits if credits is below your minimum. See Purchases for the other errors.
Dedicated Numbers
List the dedicated numbers you can buy, then buy one for 12 months with your saved card. It renews yearly on the same card.
Before you buy a number through the API, claim your free dedicated number under Settings > Sender IDs in your dashboard. That is where we collect the service address every Australian number needs, and numbers bought through the API use the same address.
GET /v1/available-numbers
List numbers available to buy, with your account's price. eligible shows whether this key can buy one right now, and ineligible_reason says why not (test_account, purchases_disabled, payment_in_review, free_number_not_claimed or no_saved_card). The saved card and daily limit are checked when you buy.
| Parameter | Type | Description |
|---|---|---|
limit (optional) | Integer | How many numbers to return, from 1 to 100. Defaults to 20. |
curl -u user123:mypassword https://api.mobilemessage.com.au/v1/available-numbers
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
'https://api.mobilemessage.com.au/v1/available-numbers',
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/available-numbers', {
headers: { 'Authorization': 'Basic ' + btoa('user123:mypassword') }
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/available-numbers');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class AvailableNumbers {
public static void main(String[] args) throws Exception {
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/available-numbers");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestProperty("Authorization", "Basic " + credentials);
try (BufferedReader in = new BufferedReader(new InputStreamReader(c.getInputStream()))) {
in.lines().forEach(System.out::println);
}
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
Console.WriteLine(await client.GetStringAsync("https://api.mobilemessage.com.au/v1/available-numbers"));
}
}
{
"status": "complete",
"price": "110.00",
"currency": "AUD",
"term_months": 12,
"eligible": true,
"results": [
{ "number": "61412345678", "previously_yours": false },
{ "number": "61487654321", "previously_yours": false }
]
}
A number your account had before and let lapse is listed first with previously_yours: true. Buying it again restores it.
POST /v1/dedicated-numbers
Buy a dedicated number. If you leave out number, the next available number is assigned. A number that was previously yours is only restored when you name it.
| Field | Type | Description |
|---|---|---|
number (optional) | String | A number from GET /v1/available-numbers. |
label (optional) | String | A label for the number in your account, up to 50 characters. |
card_last4 (optional) | String | Last 4 digits of the saved card to charge. Required when the account has more than one saved card. |
Code Examples
curl -u user123:mypassword -X POST https://api.mobilemessage.com.au/v1/dedicated-numbers \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5f0c6b2e-8d1a-4c3e-9a57-2b1f0e7d4c11" \
-d '{"label":"Support line"}'
import uuid
import requests
from requests.auth import HTTPBasicAuth
response = requests.post(
'https://api.mobilemessage.com.au/v1/dedicated-numbers',
json={"label": "Support line"},
headers={'Idempotency-Key': str(uuid.uuid4())},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.status_code, response.json())
fetch('https://api.mobilemessage.com.au/v1/dedicated-numbers', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID()
},
body: JSON.stringify({ label: 'Support line' })
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/dedicated-numbers');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Idempotency-Key: ' . bin2hex(random_bytes(16))
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["label" => "Support line"]));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
import java.util.UUID;
public class BuyNumber {
public static void main(String[] args) throws Exception {
String json = "{\"label\":\"Support line\"}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/dedicated-numbers");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("POST");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setRequestProperty("Idempotency-Key", UUID.randomUUID().toString());
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
client.DefaultRequestHeaders.Add("Idempotency-Key", Guid.NewGuid().ToString());
var content = new StringContent("{\"label\":\"Support line\"}", Encoding.UTF8, "application/json");
var resp = await client.PostAsync("https://api.mobilemessage.com.au/v1/dedicated-numbers", content);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{
"status": "complete",
"number": "61412345678",
"label": "Support line",
"renew_date": "2027-09-25",
"amount": "110.00",
"currency": "AUD",
"card_last4": "4242",
"payment_intent_id": "pi_3Q..."
}
Returns HTTP 201 when the number is on your account and ready to send from. The number also appears in GET /v1/senders.
| Code | HTTP | Meaning |
|---|---|---|
free_number_not_claimed | 403 | Claim your free dedicated number in your dashboard first. |
invalid_number | 400 | number is not a valid Australian mobile number. |
number_unavailable | 404 or 409 | The number you named is not available to buy. |
no_numbers_available | 409 | No numbers are available right now. |
number_taken_after_payment | 409 | The number you named went to another customer as your payment completed. The response includes charged: true, and our team will contact you to arrange another number or a refund. |
See Purchases for the card, limit and payment errors.
Inbound SMS
GET /v1/inbound
Retrieve paginated inbound SMS messages and opt-out replies received on your dedicated numbers. For real-time notification of inbound messages, configure a webhook.
| Parameter | Type | Description |
|---|---|---|
from (optional) | String | Filter messages received on or after this date (YYYY-MM-DD, UTC). |
to (optional) | String | Filter messages received before this date (YYYY-MM-DD, UTC). |
limit (optional) | Integer | Results per page. Default 50, max 200. |
offset (optional) | Integer | Pagination offset. Default 0. |
curl -u user123:mypassword "https://api.mobilemessage.com.au/v1/inbound?from=2026-01-01&limit=10"
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
'https://api.mobilemessage.com.au/v1/inbound',
params={'from': '2026-01-01', 'limit': 10},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/inbound?from=2026-01-01&limit=10', {
headers: { 'Authorization': 'Basic ' + btoa('user123:mypassword') }
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/inbound?from=2026-01-01&limit=10');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
echo curl_exec($ch);
curl_close($ch);
?>
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class GetInbound {
public static void main(String[] args) throws Exception {
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/inbound?from=2026-01-01&limit=10");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestProperty("Authorization", "Basic " + credentials);
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var resp = await client.GetAsync("https://api.mobilemessage.com.au/v1/inbound?from=2026-01-01&limit=10");
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{
"status": "complete",
"total": 5,
"limit": 10,
"offset": 0,
"results": [
{
"message_id": "abc123def-456g-7890-hijk-lmnopqrstuvw",
"from": "61412345678",
"to": "61400000000",
"message": "Yes, I'd like more info",
"type": "inbound",
"received_at": "2026-01-15 09:35:00"
}
]
}
Delivery Receipts & Inbound Messages (Webhooks)
Configure webhooks to receive real-time notifications for inbound messages and delivery receipts.
Managing webhook URLs via API
You can get and update your webhook URLs programmatically using the /v1/webhooks endpoint.
GET /v1/webhooks
curl -u user123:mypassword https://api.mobilemessage.com.au/v1/webhooks
import requests
from requests.auth import HTTPBasicAuth
response = requests.get(
'https://api.mobilemessage.com.au/v1/webhooks',
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/webhooks', {
headers: { 'Authorization': 'Basic ' + btoa('user123:mypassword') }
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/webhooks');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
echo curl_exec($ch);
curl_close($ch);
?>
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class GetWebhooks {
public static void main(String[] args) throws Exception {
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/webhooks");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestProperty("Authorization", "Basic " + credentials);
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var resp = await client.GetAsync("https://api.mobilemessage.com.au/v1/webhooks");
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "complete", "webhooks": { "inbound": "https://yourapp.com/inbound", "status": null }, "has_signing_secret": true }
has_signing_secret is true when a webhook signing secret is set on your account, which means every webhook we send you carries a signature. See Verifying Webhook Signatures.
POST /v1/webhooks — set a webhook URL
| Field | Type | Description |
|---|---|---|
type | String | Webhook type: inbound or status (required). |
url | String | The HTTPS URL to receive webhook notifications (required). |
curl -u user123:mypassword -X POST https://api.mobilemessage.com.au/v1/webhooks \
-H "Content-Type: application/json" \
-d '{"type":"inbound","url":"https://yourapp.com/sms-inbound"}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.post(
'https://api.mobilemessage.com.au/v1/webhooks',
json={"type": "inbound", "url": "https://yourapp.com/sms-inbound"},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/webhooks', {
method: 'POST',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ type: 'inbound', url: 'https://yourapp.com/sms-inbound' })
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/webhooks');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["type" => "inbound", "url" => "https://yourapp.com/sms-inbound"]));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class SetWebhook {
public static void main(String[] args) throws Exception {
String json = "{\"type\":\"inbound\",\"url\":\"https://yourapp.com/sms-inbound\"}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/webhooks");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("POST");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var json = "{\"type\":\"inbound\",\"url\":\"https://yourapp.com/sms-inbound\"}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var resp = await client.PostAsync("https://api.mobilemessage.com.au/v1/webhooks", content);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "complete", "message": "Webhook subscribed successfully", "type": "inbound", "url": "https://yourapp.com/sms-inbound" }
The URL must be 1024 characters or fewer and resolve to a public host — URLs pointing at private or internal addresses are rejected with HTTP 400.
DELETE /v1/webhooks — remove a webhook URL
| Field | Type | Description |
|---|---|---|
type | String | Webhook type to remove: inbound or status (required). |
curl -u user123:mypassword -X DELETE https://api.mobilemessage.com.au/v1/webhooks \
-H "Content-Type: application/json" \
-d '{"type":"inbound"}'
import requests
from requests.auth import HTTPBasicAuth
response = requests.delete(
'https://api.mobilemessage.com.au/v1/webhooks',
json={"type": "inbound"},
auth=HTTPBasicAuth('user123', 'mypassword')
)
print(response.json())
fetch('https://api.mobilemessage.com.au/v1/webhooks', {
method: 'DELETE',
headers: {
'Authorization': 'Basic ' + btoa('user123:mypassword'),
'Content-Type': 'application/json'
},
body: JSON.stringify({ type: 'inbound' })
})
.then(r => r.json())
.then(console.log);
<?php
$ch = curl_init('https://api.mobilemessage.com.au/v1/webhooks');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, "user123:mypassword");
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode(["type" => "inbound"]));
echo curl_exec($ch);
curl_close($ch);
?>
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Base64;
public class DeleteWebhook {
public static void main(String[] args) throws Exception {
String json = "{\"type\":\"inbound\"}";
String credentials = Base64.getEncoder().encodeToString("user123:mypassword".getBytes());
URL url = new URL("https://api.mobilemessage.com.au/v1/webhooks");
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("DELETE");
c.setRequestProperty("Authorization", "Basic " + credentials);
c.setRequestProperty("Content-Type", "application/json");
c.setDoOutput(true);
try (OutputStream os = c.getOutputStream()) { os.write(json.getBytes("utf-8")); }
// Handle response...
}
}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class Program {
static async Task Main() {
var client = new HttpClient();
var creds = Convert.ToBase64String(Encoding.ASCII.GetBytes("user123:mypassword"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", creds);
var request = new HttpRequestMessage(HttpMethod.Delete, "https://api.mobilemessage.com.au/v1/webhooks") {
Content = new StringContent("{\"type\":\"inbound\"}", Encoding.UTF8, "application/json")
};
var resp = await client.SendAsync(request);
Console.WriteLine(await resp.Content.ReadAsStringAsync());
}
}
{ "status": "complete", "message": "Webhook unsubscribed successfully", "type": "inbound" }
Webhook URL Setup
Set your Inbound and/or Status URL in your account settings (or via the API above). These URLs will receive POST requests with a JSON payload when an event occurs.
Webhook Payload Structure
| Field | Type | Description |
|---|---|---|
to |
String | Recipient's phone number. |
message |
String | Content of the SMS. |
sender |
String | The sender's ID or phone number. |
received_at |
String | UTC timestamp when the event was received. |
type (inbound only) |
String | Either "inbound" or "unsubscribe". |
original_message_id (inbound only) |
String | The UUID of the original outbound message. Empty if no matching outbound message was found. |
original_custom_ref (inbound only) |
String | Your custom reference for the original message. Empty if none was provided. |
status (status webhooks only) |
String | Either "delivered" or "failed". |
message_id (status webhooks only) |
String | The unique message ID. |
custom_ref (status webhooks only) |
String | Your custom reference (if provided) for the outbound message. |
part_number (status webhooks only) |
Number |
The part number for this delivery receipt.
For non-concatenated messages this will be 1.
|
total_parts (status webhooks only) |
Number |
Total number of parts for the outbound message.
For non-concatenated messages this will be 1.
|
Delivery, Timeouts and Retries
Webhooks are delivered as HTTP POST requests with a 5 second connection timeout and a 5 second response timeout. Any 2xx response marks the webhook as delivered. Any other response, or a timeout, schedules a retry.
- Up to 10 attempts are made per event.
- Retries back off exponentially: 1 minute after the first failure, then 2, 4, 8, 16 and 32 minutes, capping at 60 minutes between later attempts (each delay includes a small random jitter). An endpoint that stays unreachable is retried for roughly 4 hours in total before the event is marked failed.
- Each attempt is a fresh request. If you use webhook signing, the
X-MM-TimestampandX-MM-Signatureheaders are regenerated for every attempt, so retried deliveries always pass timestamp-freshness checks. - Webhooks are not guaranteed to arrive in order. Use the payload fields (
received_at, andpart_number/total_partson status webhooks) rather than arrival order. - Respond quickly and process asynchronously: a response slower than 5 seconds counts as a failed attempt and will be retried, which can deliver the same event more than once. Make your handler idempotent, for example on
message_idpluspart_numberfor status webhooks.
Verifying Webhook Signatures (Optional)
You can generate a webhook signing secret in your account under Settings then API, in the Webhook Signing Secret section. Once a secret exists, every inbound and status webhook we POST to you carries two extra headers that let you confirm the request really came from Mobile Message before you act on it.
Signing is optional. If you have no signing secret, your webhooks are delivered exactly as before with no extra headers and nothing you already have will break.
| Header | Description |
|---|---|
X-MM-Timestamp |
The unix timestamp in seconds at the moment the request was signed. |
X-MM-Signature |
Lowercase hex HMAC-SHA256 of the signing string, keyed with your signing secret. |
The signing string is the timestamp, a full stop, then the raw request body.
{timestamp}.{raw_body}
{timestamp} is the value of the X-MM-Timestamp header and
{raw_body} is the exact body bytes we sent, read before any JSON parsing. Parsing and
re-encoding the JSON can change whitespace or key order and will produce a different signature, so
always capture the raw body first. Recompute the HMAC with your secret and compare it to
X-MM-Signature using a timing-safe comparison such as hash_equals. Reject
the request if it does not match.
We also recommend rejecting any request where X-MM-Timestamp is more than 5 minutes
away from your own clock, so an old request captured by someone else cannot be replayed at you
later. Signatures are recomputed fresh on every retry attempt, so a retried webhook always arrives
with a current timestamp and a matching signature.
abc123, the timestamp
1754640000 and the raw body {"test":1}, the signing string is
1754640000.{"test":1} and the signature is
52344b9592722e0241d82036e0920f4286bc0d47ba4624c5a1588193490a1efb.
Run your verification code against these values to confirm it produces the same result.
<?php
$secret = 'your_signing_secret';
$timestamp = $_SERVER['HTTP_X_MM_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_MM_SIGNATURE'] ?? '';
$rawBody = file_get_contents('php://input');
// Reject anything signed more than 5 minutes away from now
if ($timestamp === '' || abs(time() - (int) $timestamp) > 300) {
http_response_code(400);
exit('Stale timestamp');
}
$expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
// Only parse the body once the signature checks out
$data = json_decode($rawBody, true);
http_response_code(200);
?>
import hashlib
import hmac
import time
from flask import Flask, abort, request
SECRET = 'your_signing_secret'
app = Flask(__name__)
@app.route('/webhook-inbound', methods=['POST'])
def inbound_webhook():
timestamp = request.headers.get('X-MM-Timestamp', '')
signature = request.headers.get('X-MM-Signature', '')
raw_body = request.get_data(as_text=True)
# Reject anything signed more than 5 minutes away from now
if not timestamp or abs(time.time() - int(timestamp)) > 300:
abort(400)
expected = hmac.new(
SECRET.encode(),
f"{timestamp}.{raw_body}".encode(),
hashlib.sha256
).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(401)
# Only parse the body once the signature checks out
data = request.get_json(force=True)
return '', 200
const express = require('express');
const crypto = require('crypto');
const SECRET = 'your_signing_secret';
const app = express();
// Keep the raw body, the signature is calculated over the exact bytes we sent
app.use(express.json({
verify: (req, res, buf) => { req.rawBody = buf.toString('utf8'); }
}));
app.post('/webhook-inbound', (req, res) => {
const timestamp = req.headers['x-mm-timestamp'] || '';
const signature = req.headers['x-mm-signature'] || '';
// Reject anything signed more than 5 minutes away from now
if (!timestamp || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
return res.sendStatus(400);
}
const expected = crypto.createHmac('sha256', SECRET)
.update(`${timestamp}.${req.rawBody}`)
.digest('hex');
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(signature, 'utf8');
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.sendStatus(401);
}
// req.body holds the parsed webhook payload
console.log(req.body);
res.sendStatus(200);
});
app.listen(3000);
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public class WebhookSignature {
private static final String SECRET = "your_signing_secret";
public static boolean isValid(String timestamp, String signature, String rawBody) throws Exception {
if (timestamp == null || signature == null) {
return false;
}
// Reject anything signed more than 5 minutes away from now
long skew = Math.abs(System.currentTimeMillis() / 1000L - Long.parseLong(timestamp));
if (skew > 300) {
return false;
}
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(SECRET.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
byte[] hash = mac.doFinal((timestamp + "." + rawBody).getBytes(StandardCharsets.UTF_8));
StringBuilder expected = new StringBuilder();
for (byte b : hash) {
expected.append(String.format("%02x", b));
}
// Constant-time comparison
return MessageDigest.isEqual(
expected.toString().getBytes(StandardCharsets.UTF_8),
signature.getBytes(StandardCharsets.UTF_8)
);
}
}
using System;
using System.Security.Cryptography;
using System.Text;
class WebhookSignature {
const string Secret = "your_signing_secret";
public static bool IsValid(string timestamp, string signature, string rawBody) {
if (string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(signature)) {
return false;
}
// Reject anything signed more than 5 minutes away from now
var skew = Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(timestamp));
if (skew > 300) {
return false;
}
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(Secret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes($"{timestamp}.{rawBody}"));
var expected = Convert.ToHexString(hash).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(expected),
Encoding.UTF8.GetBytes(signature)
);
}
}
Matching a Reply to the Message It Answers
original_message_id is the same value returned as results[].message_id
when you sent the message.
Every recipient gets its own message_id. A send request with ten
recipients returns ten results, each with a different message_id, even when the message
text is identical and they all belong to the same job, order or campaign in your system. Store each
message_id against the recipient it was returned for. Keeping a single ID per job means
later recipients overwrite earlier ones and their replies stop matching.
A reply is matched on the two phone numbers involved. We link it to the most recent message sent to that mobile number from the number they replied to. If you send the same person several messages from the same sender, their reply matches the latest one.
To match on your own identifier instead, set custom_ref when you send.
It is returned on the reply as original_custom_ref, and unlike message_id
you control the value, so it can carry your own job or order number across every recipient of that
job. Those messages can also be retrieved later with
GET /v1/messages?custom_ref=YOUR_REF, which returns each recipient along with its
message_id.
Example Inbound Webhook Payload
{
"to": "61412345678",
"message": "Hello, this is message 1",
"sender": "61412345699",
"received_at": "2024-09-30 14:35:00",
"type": "inbound",
"original_message_id": "db6190e1-1ce8-4cdd-b871-244257d57abc",
"original_custom_ref": "tracking001"
}
Example Status Webhook Payload
{
"to": "61412345678",
"message": "Hello, this is message 1",
"sender": "Mobile MSG",
"custom_ref": "tracking001",
"status": "delivered",
"message_id": "044b035f-0396-4a47-8428-12d5273ab04a",
"received_at": "2024-09-30 14:35:00",
"part_number": 1,
"total_parts": 1
}
Example Status Webhook Payload (Concatenated Message)
{
"to": "61412345678",
"message": "Hello, this is a longer message that was split into multiple parts",
"sender": "Mobile MSG",
"custom_ref": "tracking001",
"status": "delivered",
"message_id": "044b035f-0396-4a47-8428-12d5273ab04a",
"received_at": "2024-09-30 14:35:00",
"part_number": 2,
"total_parts": 3
}
Inbound Webhook Examples
Below are sample implementations for receiving inbound SMS messages. If you have a webhook signing secret, add the check from Verifying Webhook Signatures before you read the payload in any of these examples.
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhook-inbound', methods=['POST'])
def inbound_webhook():
data = request.get_json(force=True)
# Extract inbound fields
to = data.get('to', '')
message = data.get('message', '')
sender = data.get('sender', '')
received_at = data.get('received_at', '')
inbound_type = data.get('type', '')
original_message_id = data.get('original_message_id', '')
original_custom_ref = data.get('original_custom_ref', '')
print("Inbound Webhook Received:")
print(f" To: {to}")
print(f" From: {sender}")
print(f" Received At: {received_at}")
print(f" Message: {message}")
print(f" Type: {inbound_type}")
print(f" Original Message ID: {original_message_id}")
print(f" Original Custom Ref: {original_custom_ref}")
# Respond with simple 'OK'
return "OK", 200
if __name__ == '__main__':
app.run(port=5000)
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook-inbound', (req, res) => {
const {
to,
message,
sender,
received_at,
type,
original_message_id,
original_custom_ref
} = req.body;
console.log("Inbound Webhook Received:");
console.log(" To:", to);
console.log(" From:", sender);
console.log(" Received At:", received_at);
console.log(" Message:", message);
console.log(" Type:", type);
console.log(" Original Message ID:", original_message_id);
console.log(" Original Custom Ref:", original_custom_ref);
res.status(200).send("OK");
});
app.listen(3000, () => {
console.log('Inbound webhook server listening on port 3000');
});
<?php
// Simple inbound webhook example in PHP
$rawData = file_get_contents('php://input');
$data = json_decode($rawData, true);
$to = $data['to'] ?? '';
$message = $data['message'] ?? '';
$sender = $data['sender'] ?? '';
$receivedAt = $data['received_at'] ?? '';
$type = $data['type'] ?? '';
$originalMessageId = $data['original_message_id'] ?? '';
$originalCustomRef = $data['original_custom_ref'] ?? '';
error_log("Inbound Webhook Received:");
error_log(" To: $to");
error_log(" From: $sender");
error_log(" Received At: $receivedAt");
error_log(" Message: $message");
error_log(" Type: $type");
error_log(" Original Message ID: $originalMessageId");
error_log(" Original Custom Ref: $originalCustomRef");
// Send a simple OK response
http_response_code(200);
echo "OK";
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
@RestController
public class InboundWebhookController {
@PostMapping("/webhook-inbound")
public ResponseEntity<String> handleInbound(@RequestBody InboundPayload payload) {
System.out.println("Inbound Webhook Received:");
System.out.println(" To: " + payload.getTo());
System.out.println(" From: " + payload.getSender());
System.out.println(" Received At: " + payload.getReceivedAt());
System.out.println(" Message: " + payload.getMessage());
System.out.println(" Type: " + payload.getType());
System.out.println(" Original Message ID: " + payload.getOriginalMessageId());
System.out.println(" Original Custom Ref: " + payload.getOriginalCustomRef());
return ResponseEntity.ok("OK");
}
}
// Example inbound payload model
class InboundPayload {
private String to;
private String message;
private String sender;
private String receivedAt;
private String type;
private String originalMessageId;
private String originalCustomRef;
// Getters and setters...
}
using Microsoft.AspNetCore.Mvc;
using System;
[ApiController]
[Route("webhook-inbound")]
public class InboundWebhookController : ControllerBase {
[HttpPost]
public IActionResult HandleInbound([FromBody] InboundPayload payload) {
Console.WriteLine("Inbound Webhook Received:");
Console.WriteLine($" To: {payload.To}");
Console.WriteLine($" From: {payload.Sender}");
Console.WriteLine($" Received At: {payload.ReceivedAt}");
Console.WriteLine($" Message: {payload.Message}");
Console.WriteLine($" Type: {payload.Type}");
Console.WriteLine($" Original Message ID: {payload.OriginalMessageId}");
Console.WriteLine($" Original Custom Ref: {payload.OriginalCustomRef}");
return Ok("OK");
}
}
public class InboundPayload {
public string To { get; set; }
public string Message { get; set; }
public string Sender { get; set; }
public string ReceivedAt { get; set; }
public string Type { get; set; }
public string OriginalMessageId { get; set; }
public string OriginalCustomRef { get; set; }
}
Status Webhook Examples
Below are sample implementations for receiving message delivery status updates. These use fields status and message_id, plus any custom reference used when sending the message.
Each status webhook also includes part_number and total_parts so you can track delivery for concatenated messages.
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhook-status', methods=['POST'])
def status_webhook():
data = request.get_json(force=True)
# Extract status-related fields
to = data.get('to', '')
message = data.get('message', '')
sender = data.get('sender', '')
custom_ref = data.get('custom_ref', '')
status = data.get('status', '')
message_id = data.get('message_id', '')
received_at = data.get('received_at', '')
part_number = data.get('part_number', 1)
total_parts = data.get('total_parts', 1)
print("Status Webhook Received:")
print(f" To: {to}")
print(f" From: {sender}")
print(f" Received At: {received_at}")
print(f" Message: {message}")
print(f" Custom Ref: {custom_ref}")
print(f" Status: {status}")
print(f" Message ID: {message_id}")
print(f" Part: {part_number}/{total_parts}")
return "OK", 200
if __name__ == '__main__':
app.run(port=5000)
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook-status', (req, res) => {
const {
to,
message,
sender,
custom_ref,
status,
message_id,
received_at,
part_number,
total_parts
} = req.body;
console.log("Status Webhook Received:");
console.log(" To:", to);
console.log(" From:", sender);
console.log(" Received At:", received_at);
console.log(" Message:", message);
console.log(" Custom Ref:", custom_ref);
console.log(" Status:", status);
console.log(" Message ID:", message_id);
console.log(" Part:", `${part_number}/${total_parts}`);
res.status(200).send("OK");
});
app.listen(3000, () => {
console.log('Status webhook server listening on port 3000');
});
<?php
$rawData = file_get_contents('php://input');
$data = json_decode($rawData, true);
$to = $data['to'] ?? '';
$message = $data['message'] ?? '';
$sender = $data['sender'] ?? '';
$customRef = $data['custom_ref'] ?? '';
$status = $data['status'] ?? '';
$messageId = $data['message_id'] ?? '';
$receivedAt = $data['received_at'] ?? '';
$partNumber = $data['part_number'] ?? 1;
$totalParts = $data['total_parts'] ?? 1;
error_log("Status Webhook Received:");
error_log(" To: $to");
error_log(" From: $sender");
error_log(" Received At: $receivedAt");
error_log(" Message: $message");
error_log(" Custom Ref: $customRef");
error_log(" Status: $status");
error_log(" Message ID: $messageId");
error_log(" Part: {$partNumber}/{$totalParts}");
http_response_code(200);
echo "OK";
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
@RestController
public class StatusWebhookController {
@PostMapping("/webhook-status")
public ResponseEntity<String> handleStatus(@RequestBody StatusPayload payload) {
System.out.println("Status Webhook Received:");
System.out.println(" To: " + payload.getTo());
System.out.println(" From: " + payload.getSender());
System.out.println(" Received At: " + payload.getReceivedAt());
System.out.println(" Message: " + payload.getMessage());
System.out.println(" Custom Ref: " + payload.getCustomRef());
System.out.println(" Status: " + payload.getStatus());
System.out.println(" Message ID: " + payload.getMessageId());
System.out.println(" Part: " + payload.getPartNumber() + "/" + payload.getTotalParts());
return ResponseEntity.ok("OK");
}
}
// Example status payload model
class StatusPayload {
private String to;
private String message;
private String sender;
private String customRef;
private String status;
private String messageId;
private String receivedAt;
private Integer partNumber;
private Integer totalParts;
// Getters and setters...
}
using Microsoft.AspNetCore.Mvc;
using System;
[ApiController]
[Route("webhook-status")]
public class StatusWebhookController : ControllerBase {
[HttpPost]
public IActionResult HandleStatus([FromBody] StatusPayload payload) {
Console.WriteLine("Status Webhook Received:");
Console.WriteLine($" To: {payload.To}");
Console.WriteLine($" From: {payload.Sender}");
Console.WriteLine($" Received At: {payload.ReceivedAt}");
Console.WriteLine($" Message: {payload.Message}");
Console.WriteLine($" Custom Ref: {payload.CustomRef}");
Console.WriteLine($" Status: {payload.Status}");
Console.WriteLine($" Message ID: {payload.MessageId}");
Console.WriteLine($" Part: {payload.PartNumber}/{payload.TotalParts}");
return Ok("OK");
}
}
public class StatusPayload {
public string To { get; set; }
public string Message { get; set; }
public string Sender { get; set; }
public string CustomRef { get; set; }
public string Status { get; set; }
public string MessageId { get; set; }
public string ReceivedAt { get; set; }
public int PartNumber { get; set; }
public int TotalParts { get; set; }
}
Testing Your Integration
Every new account includes 50 free credits, so you can test against the live API with real messages straight away.
If you'd rather test without delivering to handsets or using credits, test (sandbox) accounts are available on request. Email hello@mobilemessage.com.au and we'll set one up for you.
A test account behaves exactly like a live account at the API level, with sending disconnected:
- Requests are validated and answered exactly as on a live account: you receive real
message_idvalues, per-message results, and cost calculations in responses. - No messages are ever delivered to handsets, and no credits are consumed.
- Accepted messages are recorded with a final status, so message lookup flows can be exercised end to end. Scheduled messages are released at their scheduled time and given their final status the same way.
- Delivery receipt webhooks fire for test sends. Around 5 seconds after a message is accepted, a simulated delivery receipt is posted to your status webhook URL with the same payload as a real one, one webhook per message part. If your account has a signing secret, simulated webhooks are signed exactly like real ones.
- To test your failure handling, send to any number ending in
000(for example0412345000). That message is recorded asfailedand its delivery receipt reportsfailed. All other test messages are reported asdelivered. - Inbound messages and inbound webhooks can be simulated with the test inbound endpoint below.
- Contacts, lists, unsubscribes, idempotency keys and the other account endpoints behave exactly as live.
- ACMA sender ID registration is not available on test accounts. Registrations are lodged with the real ACMA register, so
POST /v1/acma-registrationreturns403on a test account and can only be used from a live account. - Purchases are not available on test accounts.
POST /v1/credit-purchasesandPOST /v1/dedicated-numbersreturn403with codetest_account.
This makes a test account safe for CI: your integration tests can run against the real API with no risk of live SMS traffic or charges.
Simulating Inbound Messages
Test accounts can simulate a reply with POST /v1/test-inbound. This records the inbound message on your account exactly as a real reply would, including opt-out processing when the message starts with STOP, and posts to your inbound webhook URL if one is set. The endpoint returns 403 on live accounts and allows up to 100 simulated inbound messages per hour.
| Field | Type | Description |
|---|---|---|
from | String | The mobile number the reply comes from (required). |
message | String | The reply content (required). Start it with STOP to simulate an opt-out. |
to | String | Which of your sender IDs received the reply (optional). Defaults to the sender of the last message you sent to that number. |
curl -u user123:mypassword \
-H "Content-Type: application/json" \
-d '{"from": "0412345678", "message": "Thanks, see you then!"}' \
https://api.mobilemessage.com.au/v1/test-inbound
{ "status": "success", "type": "inbound", "to": "YourBrand", "from": "61412345678", "webhook_queued": true }
The inbound webhook payload is identical to a real inbound message, including original_message_id and original_custom_ref when the reply matches a message you previously sent to that number.
Common API Errors and Solutions
| Error Message | HTTP/Message Code | Description and Solution |
|---|---|---|
Unauthorized |
401 | Invalid API username or password. Check your credentials and ensure they are encoded correctly in your request. |
Missing or invalid "messages" parameter. |
400 | The request body must contain a valid "messages" array. Ensure the JSON structure is correct and the "messages" array is present. |
Request body is not valid JSON. |
400 | The request body could not be parsed as JSON. This is often a shell-quoting issue — for example, the Windows Command Prompt strips the double quotes from the JSON. Send a valid JSON object. |
Request body must be a JSON object. |
400 | The body was valid JSON but not an object (for example a JSON array or a bare value). Wrap your fields in a single JSON object: { ... }. |
"<field>" is required. |
400 | A required field is missing or empty. The error message names the exact field — for example "list_id", "sender", or "message". |
Invalid phone number format |
error | The phone number provided is incorrectly formatted. Ensure numbers are in either Australian local or international format. |
Message content cannot be empty |
error | Your message content is empty. |
Invalid sender. You do not have permission to use this sender. |
error | You have attempted to use a sender ID not registered in your account. Register the sender ID first or select an authorised sender. To view your sender IDs, login to your account and click Settings > Sender IDs. |
The recipient has unsubscribed and cannot receive messages. |
blocked | The recipient has unsubscribed from your messages. Remove this number from your recipient list or contact the recipient directly. |
Message contains non-GSM characters |
error | Your message contains unsupported characters (e.g., emojis). Use standard GSM characters only. |
Message exceeds the maximum allowed length. |
error | Reduce your message length. Limits are: • GSM-7: 1530 characters (10 parts) • UCS-2: 670 characters (10 parts) |
Insufficient credits to send the batch of messages. |
403 | You do not have enough SMS credits for the request. Please add more credits to your account. |
Too many concurrent requests. Please wait. |
429 | Your account has reached the maximum number of simultaneous requests (5). Wait briefly before trying again. |
Invalid request method. |
405 | The HTTP method used is not supported by this endpoint. Check the documentation for the correct method (GET, POST, or DELETE). |
Message not found or is not in a scheduled state. |
404 / 409 | Returned when attempting to cancel a message that does not exist (404) or, for message_id cancels, is not currently scheduled (409). send_id and custom_ref cancels return 404 when no scheduled messages remain, and 409 while a large list send is still being prepared. A custom_ref reused for a second list send within 24 hours also returns 409. |
This phone number already exists in your contacts. |
409 | Duplicate contact blocked. Your account has duplicate contacts disabled. Use a different number or enable duplicates in your account settings. |
You have too many pending verifications. |
429 | You have reached the limit of 3 pending sender verifications. Wait for existing ones to complete or expire before adding another. |
Simple API
GET /simple/send-sms.php
When you can’t send a JSON body, use our Simple API endpoint. All parameters go in the query string.
Example request URLs
GSM-7 (default, non-GSM characters are stripped):
https://api.mobilemessage.com.au/simple/send-sms.php?api_username=YOUR_USERNAME&api_password=YOUR_PASSWORD&sender=61412345678&to=61498765432&message=Hello+there&custom_ref=OptionalRef123
Allow Unicode when required (uses UCS-2 and 70/67 per-part limits):
https://api.mobilemessage.com.au/simple/send-sms.php?api_username=YOUR_USERNAME&api_password=YOUR_PASSWORD&sender=61412345678&to=61498765432&message=Hello+🌏&unicode=true
Query parameters
| Parameter | Required? | Description |
|---|---|---|
api_username |
Yes | Your API username. |
api_password |
Yes | Your API password. |
sender |
Yes | Your approved sender ID or phone number (for example 61412345678). Use GET /v1/senders to list your available senders. |
to |
Yes | Recipient’s phone number in international format (for example 61498765432). |
message |
Yes | URL-encoded SMS text (spaces as +, special characters percent-encoded). |
custom_ref |
No | Your own tracking reference. |
unicode |
No | Set to true to allow UCS-2 when needed. If not set, non-GSM characters are stripped and GSM-7 limits apply. |
max_parts |
No | Maximum SMS parts (credits). Returns 400 if the message exceeds this limit. Default 10, range 1–99. |
ignore_unsubscribes |
No |
Set to true to bypass unsubscribe blocking for this send.
If omitted or set to false, normal unsubscribe blocking applies.
Use with caution, as bypassing your unsubscribe list could result in spam complaints.
|
Length limits
By default up to 10 parts per message (GSM-7: 1530 septets; UCS-2: 670 characters). Use max_parts to lower or raise this limit (1–99).
Response
On success, the Simple API returns a single response object.
{
"status": "success",
"message_id": "5f8b1c22-3f7a-4b5e-9a08-2e62d4f7c9b1",
"custom_ref": "dev-test-1708869123",
"to": "61412345678",
"sender": "61400000000",
"message": "API dev test message 2026-02-25 17:10:00",
"cost": 1,
"ignore_unsubscribes": true
}