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.10.1</version>
</dependency>
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 com.wgtwo.auth.BearerTokenCallCredentials
import io.grpc.ManagedChannelBuilder
import java.util.Base64

/** Use the sandbox environment for testing without authentication */
private val environment = Environment.SANDBOX

private val endpoint = when (environment) {
    Environment.SANDBOX -> "sandbox.api.wgtwo.com"
    Environment.PRODUCTION -> "api.wgtwo.com"
}
private val channel = ManagedChannelBuilder.forAddress(endpoint, 443).build()
private val stub = SmsServiceGrpc.newBlockingStub(channel).apply {
    /**
     * If you are not using the sandbox, you need to add credentials.
     * The BearerTokenCallCredentials class can be found in our auth library.
     */
    if (environment == Environment.PRODUCTION) {
        this.withCallCredentials(BearerTokenCallCredentials { "MY_CLIENT_ACCESS_TOKEN" })
    }
}

fun main(vararg args: String) {
    require(args.size == 3) { "This program requires 3 arguments: from-number to-number content" }

    val from = args[0]
    val to = args[1]
    val encodedContent = args[2]
    val content = ByteString.copyFrom(Base64.getDecoder().decode(encodedContent))

    val request = SendDataToSubscriberRequest.newBuilder().apply {
        this.fromAddress = from
        this.toSubscriber = to
        this.content = content
        // The parts below depends on your application needs.
        messageClass = MessageClass.MESSAGE_CLASS_ME_SPECIFIC
        applicationPortBuilder.apply {
            originatorPort = 1349
            destinationPort = 1349
        }
    }.build()
    println("Request:\n$request")
    val response = stub.sendDataToSubscriber(request)
    println("Response:\n$response")

    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

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