Registration
Request a verification code
GET /v1/accounts/{transport}/code/{number}
The client requests an SMS or Voice verification code for the client's PSTN number.
transport
is the stringsms
orvoice
, depending on how the client would like a verification code delivered.number
is the client's PSTN number.
Returns:
200
request was processed successfully.400
badly formattednumber
.415
invalidtransport
.413
rate limit exceeded. Too many requests.
Confirm a verification code
PUT /v1/acccounts/code/{verification_code}
Authorization: Basic {basic_auth}
{
"signalingKey" : "{base64_encoded_52_byte_key}",
"supportsSms" : false,
"registrationId" : "{14-bit number}"
}
The client submits the verification code it received via voice or SMS to the server for confirmation.
verification_code
is the code it received via voice or SMS, numeric only.basic_auth
are the authorization credentials the client would like to create. These are in the form ofBase64({number}:{password})
, wherenumber
is the client's verified PSTN number andpassword
is a randomly generated 16 byte ASCII string.signalingKey
is a randomly generated 32 byte AES key and a 20 byte HMAC-SHA1 MAC key, concatenated together and Base64 encoded.supportsSms
indicates whether a client supports SMS as a transport.registrationId
is a 14 bit integer that's randomly generated at client install time. This will be used for clients to detect whether an app has reinstalled and lost their session state.
Returns:
200
account successfully verified.401
badly formattedbasic_auth
.403
incorrectverification_code
.413
rate limit exceeded.417
number already registered.
Registering an APN or GCM id
PUT /v1/accounts/apn/
Authorization: Basic {basic_auth}
{
apnRegistrationId: "{apn_registration_id}"
}
or
PUT /v1/accounts/gcm/
Authorization: Basic {basic_auth}
{
gcmRegistrationId: "{gcm_registration_id}"
}
The client submits its APN or GCM push registration ID.
basic_auth
is the client's authorization credentials (see above).gcm_registration_id
orapn_registration_id
is the client's registration ID.
Returns:
200
request succeeded.401
invalid authentication credentials.415
badly formatted JSON.
To unregister from the server, send the same request with method DELETE
.
Registering prekeys
PUT /v1/keys/
Authorization: Basic {basic_auth}
{
lastResortKey : {
keyId: 0xFFFFFF
publicKey: "{public_key}"
identityKey: "{identity_key}"
},
keys: [
{
keyId: {key_id},
publicKey: "{public_key}",
identityKey: "{identity_key}"
},
...]
}
public_key
is a randomly generated Curve25519 public key with a leading byte of0x05
to indicate its type. This is a total of 33 bytes, base64 encoded without padding (no ==).identity_key
is a Curve25519 public key with a leading byte of0x05
to indicate its type. This is a total of 33 bytes, base64 encoded without padding (no ==). Each client should have a single identity key generated at install time.key_id
each prekey has a unique 24bit identifier. The last resort key is always 0xFFFFFF.
Returns:
200
request succeeded.401
invalid authentication credentials.415
badly formatted JSON.
Getting a contact intersection
PUT /v1/directory/tokens
Authorization: Basic {basic_auth}
{
"contacts": [ "{token}", "{token}", ..., "{token}" ]
}
token
is Base64(SHA1(E164number)[0:10]) without Base64 padding.
Returns:
400
badly formatted token(s).401
invalid authentication credentials.415
badly formatted JSON.200
request succeeded. The structure below is returned.
{
contacts: [{token="{token}", relay="{relay}", supportsSms="true"},
{token="{token}"},
...,
{token="tokenN", relay="{relay}"}]
}
token
is Base64(SHA1(E164number)[0:10]) without Base64 padding.relay
is the name of a federated node which this contact is associated with.supportsSms
indicates that the contact supports the SMS transport.
At this point the client should be fully registered.
Sending Messages
Message Format
Messages bodies sent and received by clients are a protocol buffer structure:
message PushMessageContent {
message AttachmentPointer {
optional fixed64 id = 1;
optional string contentType = 2;
optional bytes key = 3;
}
message GroupContext {
enum Type {
UNKNOWN = 0;
UPDATE = 1;
DELIVER = 2;
QUIT = 3;
}
optional bytes id = 1;
optional Type type = 2;
optional string name = 3;
repeated string members = 4;
optional AttachmentPointer avatar = 5;
}
enum Flags {
END_SESSION = 1;
}
optional string body = 1;
repeated AttachmentPointer attachments = 2;
optional GroupContext group = 3;
optional Flags flags = 4;
}
Getting a recipient's PreKey
If a client does not have an existing session with a recipient, the client will need to retrieve a PreKey for the recipient in order to start one.
GET /v1/keys/{number}/{device_id}?relay={relay}
Authorization: Basic {basic_auth}
number
is the number of the recipient.device_id
is the device id of the recipient, or*
for all devices.relay
(optional) is the federated relay the recipient is associated with. Therelay
param should only be included if the destination is at a federated node other than the sender.
Returns:
401
invalid authentication credentials.413
rate limit exceeded.404
unknown/unregisterednumber
.200
request succeeded. The structure below is returned.
{
"keys" : [
{
"deviceId": {device_id},
"keyId": {key_id},
"publicKey": "{public_key}",
"identityKey": "{public_key}"
},
...
]
}
Submitting a message
PUT /v1/messages/{destination_number}
Authorization Basic {basic_auth}
{
relay: "{relay}",
messages: [{
type: {type},
destinationDeviceId: {destination_device_id},
destinationRegistrationId: {destination_registration_id},
body: "{base64_encoded_message_body}", // Encrypted PushMessageContent
timestamp: "{time_sent_millis_since_epoc}"
},
...,
]
}
destination_number
is the PSTN number of the message recipient.relay
(optional) is the relay the message recipient is registered with.type
is the type of message. Supported types are enumerated below.destination_device_id
is the target device the message corresponds to for thedestination_number
.body
is the Base64 encoded (without padding) and encryptedPushMessageContent
(above).timestamp_sent_millis_since_epoch
is the timestamp of the message in millis since the epoch.
Returns:
401
invalid authentication credentials.409
mismatched devices.410
stale devices.413
rate limit exceeded.415
badly formatted JSON.200
request succeeded.
409 Mismatched Devices:
This return code indicates that the devices in messages
do not match the registered devices for destination_number
. The response body indicates the mismatch:
{
missingDevices: [{missing_device_id}, {another_missing_device_id}, ...],
extraDevices: [{device_id_doesnt_exist}, ...]
}
410 Stale Devices:
This return code indicates that a target device has re-installed and the requesting client is sending a message for a stale session. The response body indicates which devices are effected:
{
staleDevices: [{stale_device_id}, ...]
}
Receiving a message
APN clients will receive a push notification:
{
alert: "You have a new message!",
"m": "{payload}"
}
GCM clients will receive a push notification:
{payload}
payload
is a Base64 encoded (without padding)IncomingPushMessageSignal
, which is encrypted and MAC'd using thesignalingKey
submitted during registration.
Encrypted IncomingPushMessageSignal format:
struct {
opaque version[1];
opaque iv[16];
opaque ciphertext[...]; // The IncomingPushMessageSignal
opaque mac[10];
}
The IncomingPushMessageSignal protocol buffer:
message IncomingPushMessageSignal {
enum Type {
UNKNOWN = 0;
CIPHERTEXT = 1;
KEY_EXCHANGE = 2;
PREKEY_BUNDLE = 3;
PLAINTEXT = 4;
}
optional Type type = 1;
optional string source = 2;
optional uint32 sourceDevice = 7;
optional string relay = 3;
optional uint64 timestamp = 5;
optional bytes message = 6; // Contains an encrypted PushMessageContent
}
Attachments
Recall that a push message is transmitted as the following structure:
message PushMessageContent {
message AttachmentPointer {
optional fixed64 id = 1;
optional string contentType = 2;
optional bytes key = 3;
}
message GroupContext {
enum Type {
UNKNOWN = 0;
UPDATE = 1;
DELIVER = 2;
QUIT = 3;
}
optional bytes id = 1;
optional Type type = 2;
optional string name = 3;
repeated string members = 4;
optional AttachmentPointer avatar = 5;
}
enum Flags {
END_SESSION = 1;
}
optional string body = 1;
repeated AttachmentPointer attachments = 2;
optional GroupContext group = 3;
optional Flags flags = 4;
}
To fill out the AttachmentPointer
structure, the client takes the following steps:
- Generates a single-use 32 byte AES key and 32 byte Hmac-SHA256 key.
- Encrypts the attachment using AES in CBC mode with PKCS#5 padding and a random IV, then formats the encrypted blob as
IV || Ciphertext || MAC
. - Requests an attachment allocation from the server.
- Uploads the attachment to the allocation.
- Constructs the
AttachmentPointer
with the attachment allocationid
, the attachment's MIMEcontentType
, and the concatenated 32 byte AES and 32 byte Hmac-SHA256key
.
Allocating an attachment
GET /v1/attachments/
Authorization: {basic_auth}
Returns:
401
invalid authentication credentials.413
rate limit exceeded.200
request succeeded. The structure below is returned.
{
"id" : "{attachment_id}",
"location" : "{attachment_url}"
}
Uploading an attachment
PUT {attachment_url}
Content-Type: application/octet-stream
The client PUT
s the encrypted binary blob to the attachment_url
returned from the attachment allocation step.
Retrieving an attachment
GET /v1/attachments/{attachment_id}
Authorization: {basic_auth}
attachment_id
is theid
in a receivedAttachmentPointer
protocol buffer.
Returns
401
invalid authentication credentials.413
rate limit exceeded.200
request succeeded. The structure below is returned.
{
"location" : "{attachment_url}"
}
The client can now GET {attachment_url}
to retrieve the encrypted binary blob.