Send data SMS to subscriber

This part of the SMS API enables a third party to send data (binary) SMSes to a subscriber. Data messages are used to let applications talk to each other without requiring data network access, or where the data network is unreliable.

The same type of SMS messages are also used for a number of other network specific and SIM related management, and this both have more options than sending text messages, and more restrictions.

To see what can be set as the sender address consult the API for Sending text SMS to subscriber.

Note that some device receiver applications have stricter requirements on the message than common text SMS apps. E.g. many APN config update apps have stricter limits to the length and formatting of the sender address, e.g allows max 22 chars.

Fragmentation and UDH

We do not allow callers to the API to freely set the UDH (User Data Header) themselves. If the content data is longer than 140 bytes (or 133 bytes if application port it set), we will fragment the message the same way as with other messages to subscriber, and application port is set from the request if provided.

Prerequisites

  1. An OAuth 2.0 client
  2. A client access token

Required scope

  • sms.data:send_to_subscriber is required to use the API function.

Code

TIP

You can test our APIs without authorization by targetting sandbox.api.wgtwo.com instead of api.wgtwo.com and removing any authorization from the request/code sample.

Download proto definitions
curl -sL 'https://github.com/working-group-two/wgtwoapis/blob/master/image.bin?raw=true' -o wgtwo.bin
1
export ACCESS_TOKEN="my_client_access_token"
export BASE64_DATA="TXkgZGF0J2EgbeKCrMKrwrs2wrMK"
grpcurl -protoset wgtwo.bin \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d @ \
  api.wgtwo.com:443 \
  wgtwo.sms.v1.SmsService/SendDataToSubscriber <<EOM
  {
    "content": $(echo "$BASE64_DATA" | base64 -d | jq -R),
    "toSubscriber": "+47xxxxxxxx",
    "fromAddress": "MyProduct",
    "messageClass": "MESSAGE_CLASS_ME_SPECIFIC",
    "applicationPort": {
      "originatorPort": 1349,
      "destinationPort": 1349
    }
  }
EOM
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Example result

{
  "messageId": "6a75356e-6191-11ec-b47d-7382e9b102b6",
  "status": "SEND_STATUS_OK",
  "numFragments": 1
}
1
2
3
4
5
Install dependencies

Maven

<dependency>
  <groupId>com.wgtwo.api.v1.grpc</groupId>
  <artifactId>sms</artifactId>
  <version>1.8.0</version>
</dependency>

search.maven.org/search?q=g:com.wgtwo.api.v1.grpcopen in new window

package com.example.sms

import com.google.protobuf.ByteString
import com.wgtwo.api.v1.sms.SmsProto.MessageClass
import com.wgtwo.api.v1.sms.SmsProto.SendDataToSubscriberRequest
import com.wgtwo.api.v1.sms.SmsServiceGrpc
import io.grpc.CallCredentials
import io.grpc.ManagedChannelBuilder
import io.grpc.Metadata
import io.grpc.Status
import java.util.Base64
import java.util.concurrent.Executor
import java.util.function.Supplier

fun main(vararg args: String) {
    class BearerToken(private val tokenSupplier: Supplier<String>) : CallCredentials() {
        private val authHeader = Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER)

        override fun applyRequestMetadata(
            requestInfo: RequestInfo,
            executor: Executor,
            metadataApplier: MetadataApplier,
        ) {
            executor.execute {
                try {
                    val headers = Metadata()
                    val token = tokenSupplier.get()
                    headers.put(authHeader, "Bearer $token")
                    metadataApplier.apply(headers)
                } catch (e: Throwable) {
                    metadataApplier.fail(Status.UNAUTHENTICATED.withCause(e))
                }
            }
        }

        override fun thisUsesUnstableApi() {}
    }

    if (args.size != 3) {
        throw IllegalArgumentException(
            "This program requires 3 arguments: from-number to-number content",
        )
    }
    val accessToken = "my_client_access_token"
    val api = "api.wgtwo.com" // or use "sandbox.api.wgtwo.com"
    val channel = ManagedChannelBuilder.forAddress(api, 443).build()
    try {
        val service = SmsServiceGrpc.newBlockingStub(channel)
            .withCallCredentials(BearerToken { accessToken })
            .withWaitForReady()
        println(
            service.sendDataToSubscriber(
                SendDataToSubscriberRequest.newBuilder().apply {
                    fromAddress = args[0]
                    toSubscriber = args[1]
                    content = ByteString.copyFrom(Base64.getDecoder().decode(args[2]))
                    // The parts below depends on your application needs.
                    messageClass = MessageClass.MESSAGE_CLASS_ME_SPECIFIC
                    applicationPortBuilder.apply {
                        originatorPort = 1349
                        destinationPort = 1349
                    }
                }.build(),
            ),
        )
    } finally {
        channel.shutdownNow()
    }
}
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

Example result

message_id: "6a75356e-6191-11ec-b47d-7382e9b102b6"
status: SEND_STATUS_OK
num_fragments: 1
1
2
3

Handling the binary SMS

The binary SMS also needs to be handled on the receiving device, which will need some application on the device to know what to do with the message. Most third party developers will not have access to create messages that can harm the handset directly, see using the messageClass and applicationPort below.

Setting the message class

The binary SMS also needs to be handled on the receiving device. And in case of UNSPECIFIED, FLASH_MESSAGE and ME_SPECIFIC messages, they need to be handled by an application on the device.

Note that SIM_SPECIFIC and TE_SPECIFC are handled by the SIM card itself or the hardware (non-programmable part) of the handset itself respectively, and have more restricted access than the FLASH_MESSAGE and ME_SPECIFIC classes. If not set the message will not have a message class specified (aka UNSPECIFIED).

Setting the destination port number

Binary SMS messages also usually needs a port number to route it to a specific application. It is important that the destinationPort in the API request and the android:port configuration (or equivalent for iOS) matches. This way your data messages will be sent to your application.

Example receiver application

Android devices can install apps that listen to SMS messages, including data messages. See google developer trainingopen in new window for more detailed howto, and androidopen in new window for details on the SMS itself.

package com.example;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.util.Log;
import java.nio.charset.StandardCharsets;

public class DataSmsReceiver extends BroadcastReceiver {
    private static final String TAG = DataSmsReceiver.class.getName();

    @TargetApi(Build.VERSION_CODES.M)
    @Override
    public void onReceive(Context context, Intent intent) {
        Bundle bundle = intent.getExtras();
        if (bundle != null) {
            String format = bundle.getString("format");
            Object[] incomingPDUs = (Object[]) bundle.get("pdus");
            byte[][] dataParts = new byte[incomingPDUs.length];
            int totalLength = 0;

            for (int i = 0; i < msgs.length; i++) {
                SmsMessage msg = SmsMessage.createFromPdu((byte[]) incomingPDUs[i], format);
                dataParts[i] = msg.getUserData();
                totalLength += dataParts[i].length;
            }

            byte[] content = new byte[totalLength];
            int pos = 0;
            for (byte[] part : dataParts) {
                System.arraycopy(part, 0, content, pos, part.length);
                pos += part.length;
            }
            Log.d(TAG, "Received raw data: " + new String(content, StandardCharsets.UTF_8));
        }
    }
}
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
26
27
28
29
30
31
32
33
34
35
36
37
38

The receiver also needs to be connected to the data SMS.

<receiver android:name="com.example.DataSmsReceiver"
          android:enabled="true"
          android:exported="true">
    <intent-filter android:priority="10">
        <action android:name="android.intent.action.DATA_SMS_RECEIVED"/>
        <data android:scheme="sms"
              android:host="*"
              android:port="1349"/>
    </intent-filter>
</receiver>
1
2
3
4
5
6
7
8
9
10

NOTE: There are strict limitations preventing applications from listening to SMS messages, including data messages on iOS devices.

Read more