Migrating from the legacy REST API to the Mobile Messaging API¶
Why we are doing this¶
The Mobile Messaging API replaces our legacy REST API (/rest/mtsms). It is a smaller, simpler API designed to cut out manual setup. Decisions you used to make up front - message class, character encoding, per-message callback URLs and credit checks - are now handled automatically on our side.
Here are the main benefits:
- Ready for RCS. Integrate once, and messages are automatically delivered as RCS where the recipient supports it, falling back to SMS otherwise. No code changes needed when RCS becomes available on a route.
- Fewer parameters to get wrong. Message class and character encoding are determined by us.
- Simpler authentication. Token only.
- Cleaner delivery reporting. One webhook configuration, one event envelope, HMAC-signed.
What changes at a glance¶
| Legacy REST API | Mobile Messaging API | |
|---|---|---|
| Host | gatewayapi.com / gatewayapi.eu |
messaging.gatewayapi.com / messaging.gatewayapi.eu |
| Send endpoint | POST /rest/mtsms |
POST /mobile/single and POST /mobile/multi |
| Auth | Token, Basic or OAuth1 | Token only |
| Request formats | JSON, form data, query string, GET | JSON only |
| Structure | One message, many recipients | One message object per recipient |
| Message class | class parameter |
Removed - determined automatically |
| Encoding | encoding: UTF8 / UCS2 |
Removed - detected automatically. UCS-2 messages can contain fewer characters per SMS part, which can increase the number of parts and cost. |
| Callbacks | callback_url per message, or default webhook |
Configured webhooks only |
| Success status | 200 OK |
202 Accepted |
| Message ID | Integer, e.g. 421332671 |
String, e.g. 01JNN696A9E0WS89FPYGT15NBX |
| Price in response | usage.total_cost |
Not returned |
| Insufficient credit | Request rejected up front | Message accepted, REJECTED delivery report follows |
| Status lookup | GET /rest/mtsms/{id} |
Not available - use webhooks |
1. Authentication¶
Only the Token scheme is supported. Basic auth and OAuth1 are not carried over.
1 | |
Existing API keys are managed in the dashboard (app.gatewayapi.com / app.gatewayapi.eu) as
before. Note that keys are tied to a platform - an EU key works against messaging.gatewayapi.eu.
Authentication failures now return 403, where the legacy API returned 401.
2. Sending a single message¶
Legacy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
New
1 2 3 4 5 6 7 8 9 10 11 | |
Four fields fewer, and the two that remain changed name (recipients[].msisdn → recipient, userref → reference).
Response - 202 Accepted
1 2 3 4 5 | |
Field notes:
sender- now required. 3-18 characters; still subject to the usual limits of 11 alphanumeric or 15 numeric characters at the destination. Characters outside[A-Za-z0-9]carry a higher risk of replacement or rejection downstream.message- UTF-8, up to 39,015 characters.reference- optional, echoed back in the response and in every webhook event for the message.
3. Message class is gone¶
The class parameter (standard, premium, secret) no longer exists. We resolve the right class during processing based on account configuration, recipient network, sender and other factors. Most notably, urgent priority resolves to SMS PLUS and may be priced as premium.
Action for customers: delete the field. There is no replacement and no fallback - sending it is simply unnecessary.
4. Encoding is detected automatically¶
The encoding parameter (UTF8 / UCS2) is gone. Send the message text as UTF-8 and we pick the correct on-the-wire encoding per message:
- If the text fits GSM-7, it is sent as GSM-7 - 160 characters in a single part, 153 per part when concatenated.
- If it contains characters GSM-7 cannot represent (Greek, Cyrillic, Arabic, Chinese, most emoji), it is sent as UCS-2 - 70 characters in a single part, 67 per part when concatenated.
This removes the most common integration bug on the legacy API: forgetting to set UCS2, and customers seeing question marks or mangled characters at the handset.
Action for customers: delete the field. If your code branched on “does this text need UCS-2?”, delete that logic too.
5. Sending in batches¶
The batching model has changed shape. On the legacy API, a batch was one message with many recipients. On the new API, a batch is many complete messages, each with its own recipient.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | |
Response - 202 Accepted
1 2 3 4 5 6 | |
Each message in the batch is unpacked and processed individually, exactly as if it had been sent to /mobile/single.
Limits: up to 1,000 messages per request (the legacy limits of 10,000 recipients and 1,000messages per request no longer apply in that form).
Personalisation replaces tags / tagvalues. Legacy customers who used tag substitution ("Hello %Firstname%" plus a tagvalues array per recipient) now build the final text themselves and send one message object per recipient. This is more verbose on the wire, but it is a straightforward loop in every language, and it removes the class of bug where the tag count and value count did not line up.
Connection guidance is unchanged: batch rather than opening large numbers of parallel connections. Delivery speed is set by the operators - 1 request with 1,000 messages is delivered at the same speed as 10 requests with 100.
6. Callbacks are now configured webhooks¶
callback_url per message is gone. Delivery reports and incoming messages are delivered to the webhooks configured on the account in the dashboard.
Customers who used a single callback_url on every message: configure that URL once as a webhook and delete the field.
Customers who varied callback_url per message to route traffic internally: use the reference field to carry your own routing key, and dispatch on it when the event arrives. reference is echoed back in every webhook event for the message.
The payload has changed. All webhook events now use a shared envelope:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
The event_type field determines the schema of event. Current types: message.status.sms, message.status.rcs, user-message.text.sms, user-message.text.rcs, user-message.location.rcs, user-message.file.rcs.
Key differences from the legacy delivery status callback:
- Status fields sit inside
event, not at the top level. - Timestamps are ISO 8601 strings, not Unix integers.
userrefis nowreference.erroris an object (hex_code,details) rather than separateerrorandcodefields.- Handlers must tolerate unknown
event_typevalues and additional fields - new event types are not considered breaking changes.
Signature verification has changed. The legacy API sent a JWT in X-Gwapi-Signature. The new API sends an HMAC-SHA-256 of the raw request body, hex-encoded, in the Signature header as v1=<hex-encoded-hmac>, using the secret configured for the webhook. Verify against the raw body bytes, before any JSON parsing.
Response expectations are unchanged: reply 2xx and we consider the event delivered; anything else and we retry later.
7. The response no longer contains an ID you recognise - or a price¶
Two changes that customers will notice immediately.
Message IDs unique reference string, not integers.
| Legacy | New |
|---|---|
421332671 |
01JNN696A9E0WS89FPYGT15NBX |
The message IDs are now a string. Currently we use the ULID format, but it might be changed to a different identifier format in the future. Customers storing message IDs must widen the column - an integer column will not hold one. This is the single most likely cause of a failed migration, and it fails at the database layer, not the API layer, so it is worth calling out prominently. We recommend treating the message IDs as a string of variable length.
No usage or total_cost in the response. The legacy API checked available credit before accepting the message and returned the cost. The new API accepts the message and settles cost downstream.
This means insufficient credit surfaces differently. Previously the send request failed. Now the request is accepted with 202, and the customer receives a delivery report with status REJECTED:
1 2 3 4 5 6 7 8 9 10 11 | |
Action for customers: if your system relied on a failed API call to detect an empty account, move that detection into your webhook handler and alert on REJECTED. Balance monitoring and auto-top-up in the dashboard remain the recommended first line of defence.
8. Field mapping reference¶
| Legacy field | New field | Notes |
|---|---|---|
message |
message |
Unchanged. UTF-8, max 39,015 characters |
sender |
sender |
Now required, 3-18 characters |
recipients[].msisdn |
recipient |
One message object per recipient |
userref |
reference |
Echoed in response and all webhook events |
label |
label |
Unchanged. Max 128 characters, statistics only |
class |
- | Removed, determined automatically |
encoding |
- | Removed, detected automatically |
callback_url |
- | Removed, configure webhooks in the dashboard |
validity_period (seconds) |
expiration |
See below |
priority |
priority |
BULK / NORMAL → normal; URGENT / VERY_URGENT → urgent |
tags / tagvalues |
- | Build the final message text per recipient |
sendtime |
- | Not available |
destaddr (flash SMS) |
- | Not available |
payload / udh (binary SMS) |
- | Not available |
max_parts |
- | Not available |
extra_details |
- | Not available |
expiration replaces validity_period and accepts several formats. ISO 8601 duration is recommended: PT1H, PT10M, P1D. An absolute ISO 8601 date-time (2026-02-19T18:00:00Z), a Unix timestamp, or a plain seconds offset also work. Default and maximum are both 5 days (P5D) - the same as the legacy API, so a legacy validity_period of 86400 becomes "PT24H" or simply 86400.
priority collapses four values into two. urgent activates SMS PLUS and may be priced higher depending on account configuration; it applies to SMS only, as RCS has no priority system.
9. Features without a direct equivalent¶
These exist on the legacy API and are not part of the Mobile Messaging API. Usage is low, but Support should be ready to identify affected accounts.
- Scheduled sending (
sendtime) and cancelling a scheduled message (DELETE /rest/mtsms/{id}) - Status polling (
GET /rest/mtsms/{id}). Deliberately omitted - polling generates large volumes of requests that mostly report no change. Webhooks push the state to you when it actually changes. - Binary SMS (
payload,udh) and flash SMS (destaddr: DISPLAY) - Tag substitution (
tags/tagvalues) - see section 5 for the replacement pattern - Part limiting (
max_parts) and per-recipient usage detail (extra_details) - Overcharged / premium SMS (
chargeon recipients,charge_statusin callbacks)
10. Suggested migration checklist for customers¶
- Generate an API token in the dashboard if you are still on Basic auth or OAuth1.
- Configure a webhook, with a secret, and implement HMAC-SHA-256 verification against the raw body.
- Widen your message ID storage to hold a 26-character string.
- Point requests at
messaging.gatewayapi.com(or.eu) and switch to/mobile/single. - Remove
class,encodingandcallback_url. Renameuserreftoreferenceandvalidity_periodtoexpiration. - Update your webhook handler for the new envelope, ISO timestamps and the
errorobject - and add handling forREJECTEDand0x1092. - Convert batch sends to
/mobile/multiwith one message object per recipient, building personalised text yourself. - Run both integrations side by side against a small volume before cutting over.
Node.js customers can use the type-safe SDK: npm install @onlinecity/gatewayapi-node (documentation and examples: https://github.com/onlinecity/gatewayapi-node).
Full API reference: OpenAPI spec, with Swagger and Redoc.