Skip to content

Mobile Messaging API

The Mobile Messaging API is a simple and future-proof interface for sending SMS and RCS messages to phone numbers. By integrating with this API, you are automatically prepared for RCS messaging. Messages seamlessly fall back to SMS when recipients are not RCS-capable, without requiring any changes to your code. The API handles SMS-specific details such as character encoding and message classes automatically.

Read more in our help center.

Tip

If you are using Node.js, we offer a type-safe Node.js SDK for the Mobile Messaging API.

npm install @onlinecity/gatewayapi-node

You can find documentation and examples here.

API endpoint

All API calls must be made against one of the following domains, depending on your setup:

  • messaging.gatewayapi.com
  • messaging.gatewayapi.eu

Each API domain is secured with a digital certificate provided by a widely known and used certificate authority, the certificates are frequently and automatically renewed.

The systems that terminate the encrypted connection on our end is configured to require TLS version 1.2.

Authentication

Authentication is performed using the Authentication header with the Token Scheme with the value being an API that is valid for the given domain.

API keys can be generated and managed in the GatewayAPI Dashboard or its EU variant.

Sending a message

A minimal example for sending a single message:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
POST /mobile/single HTTP/1.1
Host: messaging.gatewayapi.com
Authorization: Token YOUR_API_TOKEN
Accept: application/json
Content-Type: application/json

{
    "sender": "ExampleSMS",
    "message": "Hello world!",
    "recipient": 4512345678
}

The successful response looks like:

1
2
3
4
5
{
    "msg_id": "01JNN696A9E0WS89FPYGT15NBX",
    "recipient": 4512345678,
    "reference": "<client provided reference>"
}

We provide a full OpenAPI spec, as well as Swagger and Redoc.

Optional fields

The following optional fields can be included when sending a message.

label

1
2
3
{
    "label": "2fa-login"
}
A client-defined label used to retrieve statistics grouped by label via our Statistics API or directly within the self-service Customer Dashboard App The label field is compatible with the existing label feature used across GatewayAPI and can be used to retrieve usage statistics grouped by label via the Statistics API.

  • The label is stored for reporting purposes only
  • It is not returned in the send-message response
  • It is not included in webhook payloads
  • It does not affect message delivery or routing

reference

1
2
3
{
    "reference": "order-12345"
}
A client-defined reference that is echoed back in delivery reports and webhook callbacks.

expiration

1
2
3
{
    "expiration": "PT1H"
}
Specifies how long the message is valid for delivery. If the message cannot be delivered before the expiration time, it will expire and no further delivery attempts will be made. If not set, the default expiration is 5 days. This is also the max expiration.

The value can be provided in one of the following formats:

  • ISO 8601 duration (recommended). Example: PT1H (1 hour), PT10M (10 minutes), P1D (1 day)
  • ISO-8601 date-time (absolute expiration time). Format: YYYY-MM-DD[T]HH:MM:SS[.ffffff][Z or [+ or -]HH[:]MM]. Example: 2026-02-19T18:00:00Z
  • Unix timestamp (seconds since epoch). Example: 1760896800
  • Seconds offset (relative expiration time). Example: 18000 (expires in 5 hours)

Error Responses

If the API is unable to process the request, an error response is generated and sent back instead of a response confirming the message submission. HTTP status codes are used to communicate this status.

Futhermore each error response contains a json structure to indicate what went wrong, which can be helpful to debug the problem, regardless of what the error is, it can be a good idea to log the content for later troubleshooting.

An overview of HTTP status codes can be read at the MDN page on status codes.

Below is a list of common request error status codes - their meaning with some more specific details to our system, as well as some instructions on how to begin troubleshooting them.

400

The API could not parse the request, make sure the request has correctly formed content. Verify the content of the message is json, and that the Content-Type header is set.

403

The API could not authenticate the request, please verify the request credentials to make sure your credentials match API keys in the dashboard. Consider reading the section on authentication again to make sure you got it right.

422

The API could parse the request, but the parsed data did not adhere to the requirements set by the API for messages. Some examples of why this can happen can be that the request did not include a recipient, or the recipient was not a phone number, or that the request did not include an actual text message to send to an otherwise valid recipient. Or maybe the message was set to expire some time ago or too long into the future or perhaps the sender field in the message did not live up to the requirements for senders.

This is not an exhaustive list of cases that can cause this error, all the fields and their requirements are listed in the API specification.

The response content contains some data in json format with information about which field caused what problem, which can be matched up against API specification to see what adjustments needs to be made to the request.

500

An unexpected error occurred within the API code. This should be a rare occurence, and the error should already be saved and the team notified. Some errors are caused by temporary problems in other systems, so it is possible that the message can be send in a new request a short while later.

Consider visiting our statuspage if the problem persists.

Webhook Callbacks

Whenever a message sent via the Mobile Messaging API changes state, it produces an event that our systems then attempt to deliver to the configured webhook.

More information about webhooks in general can be found at the Webhooks page.

Polling APIs

By design the Mobile Message API does not include APIs for polling message states or user generated events (such as messages).

Our reason for doing so, is that polling solutions often make many requests that ultimately results in no action, such as the state of a message is still the same in both systems and thus no change to the saved state is required.

Webhooks offer a superior solution where instead of periodic requests to see if an action needs to be taken, our system will notify your systems when an action needs to be taken.

All webhook callbacks use a consistent event envelope format:

1
2
3
4
5
6
7
8
{
    "event_id": "09c829a2-a77b-422d-8562-2f88e784dfb5",
    "timestamp": "2025-04-01T11:50:12.003029+00:00",
    "event_type": "<event type indicator string>",
    "event": {
        ...
    }
}

The event_type field indicates the type of event and determines the schema of the event object. Supported event types:

  • message.status.sms
  • message.status.rcs
  • user-message.text.sms
  • user-message.text.rcs
  • user-message.location.rcs
  • user-message.file.rcs

SMS status callback example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
    "event_id": "09c829a2-a77b-422d-8562-2f88e784dfb5",
    "timestamp": "2025-04-01T11:50:12.003029+00:00",
    "event_type": "message.status.sms",
    "event": {
        "msg_id": "01JQRJWK259Y1YEECJZB50908V",
        "recipient": 4512345678,
        "reference": "<client provided reference>",
        "status": "DELIVERED",
        "status_at": "2025-04-01T11:49:12.005021+00:00",
        "error": null
    }
}

RCS status events are similar to SMS, but the status field can only contain EXPIRED, DELIVERED, ENROUTE and READ:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
    "event_id": "09c829a2-a77b-422d-8562-2f88e784dfb5",
    "timestamp": "2025-04-01T11:50:12.003029+00:00",
    "event_type": "message.status.rcs",
    "event": {
        "msg_id": "01JQRJWK259Y1YEECJZB50908V",
        "recipient": 4512345678,
        "reference": "<client provided reference>",
        "status": "READ",
        "status_at": "2025-04-01T11:49:12.005021+00:00"
    }
}

Incoming SMS message payload:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
    "event_id": "09c829a2-a77b-422d-8562-2f88e784dfb5",
    "timestamp": "2025-04-01T11:50:12.003029+00:00",
    "event_type": "user-message.text.sms",
    "event": {
        "msg_id": "01K33QFGSHXXM0Q7HM7QS34PY6",
        "sender": 4512345678,
        "sent_at": "2025-04-01T11:49:12.005021+00:00",
        "content": {
            "message": "Hello World",
            "encoding": "UCS2",
            "header" : "050003CC0201"
        }
    }
}

Incoming RCS message payload:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
    "event_id": "09c829a2-a77b-422d-8562-2f88e784dfb5",
    "timestamp": "2025-04-01T11:50:12.003029+00:00",
    "event_type": "user-message.text.rcs",
    "event": {
        "msg_id": "01K33QFGSHXXM0Q7HM7QS34PY6",
        "sender": 4512345678,
        "content": "Hello world!",
        "sent_at": "2025-04-01T11:49:12.005021+00:00"
    }
}

RCS allows for more complex payloads for file and location information from the end user, which is reflected in the content field being an object.

RCS location example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "event_id": "09c829a2-a77b-422d-8562-2f88e784dfb5",
    "timestamp": "2025-04-01T11:50:12.003029+00:00",
    "event_type": "user-message.location.rcs",
    "event": {
        "msg_id": "01K33QFGSHXXM0Q7HM7QS34PY6",
        "sender": 4512345678,
        "content": {
            "latitude": 55.6806024,
            "longitude": 12.5790722
        },
        "sent_at": "2025-04-01T11:49:12.005021+00:00"
    }
}

Files are hosted on a Google-provided CDN. If you need to retain files, you should download them, as long-term availability is not guaranteed. Depending on the file type, fields such as thumbnail and category may be null.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
    "event_id": "09c829a2-a77b-422d-8562-2f88e784dfb5",
    "timestamp": "2025-04-01T11:50:12.003029+00:00",
    "event_type": "user-message.file.rcs",
    "event": {
        "msg_id": "01K33QFGSHXXM0Q7HM7QS34PY6",
        "sender": 4512345678,
        "content": {
            "category": null,
            "thumbnail": {
                "mime_type": "image/jpeg",
                "size_bytes": 5246,
                "name": null,
                "uri": "https://rcs-<region>.googleapis.com/blob/<conversation-id>/<object-id>"
            },
            "payload": {
                "mime_type": "image/jpeg",
                "size_bytes": 24285,
                "name": "9112194760767796361.jpg",
                "uri": "https://rcs-<region>.googleapis.com/blob/<conversation-id>/<object-id>"
            }
        },
        "sent_at": "2025-04-01T11:49:12.005021+00:00"
    }
}

Webhook Signature Verification

All webhook events are signed and timestamped to verify their authenticity and integrity as originating from GatewayAPI.

The signature is provided in the Signature header using the format: v1=<hex-encoded-hmac>

To verify the signature: Calculate an HMAC-SHA-256 value of the raw request body using the secret key configured for the webhook in the GatewayAPI dashboard. Hex-encode the result and compare it with the value provided in the Signature header.

Key length

Longer secret keys increase the cryptographic strength of HMAC signatures. Beyond a certain length, however, the added security benefit becomes negligible, as the effort required to brute-force the key is already impractical.

For this reason, GatewayAPI enforces a maximum length for webhook secret keys to ensure strong security without unnecessary processing overhead.

An example of a function that can check if the request is signed as expected, could be:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import hmac
import secrets

class Request:
    """Generic request model."""

    body: bytes
    headers: dict[str, str]

def verify_request(request: Request, signature_key: bytes) -> bool:
    signature = request.headers.get("Signature")
    if not signature:
        return False

    expected_signature = hmac.digest(
        signature_key,
        request.body,
        "SHA256",
    )
    return secrets.compare_digest(
        signature.removeprefix("v1="),
        expected_signature.hex(),
    )

Webhook events include a timestamp, allowing you to safely reject old or replayed requests.

Changelog & compatibility

The Mobile Messaging API is designed to be stable and reliable. We do not introduce breaking changes without proper deprecation warnings.

GatewayAPI places a strong emphasis on API stability. Once an integration is in place, existing functionality will not change without advance notice and an appropriate deprecation period.

A breaking change is defined as the removal of existing fields or a fundamental change in their meaning.

The addition of new fields, options, or event types is not considered a breaking change. Integrations should therefore be implemented defensively and tolerate additional fields in API responses and webhook payloads.

As the API evolves, new event types may be introduced to provide additional insight into your messaging traffic

January 2026 - Added label parameter

The API now accepts the optional label parameter. This is to be backwards compatible with the existing label feature from the legacy REST API which gives usage statistics by label.