This is the current API V1 documentation. Explore our API V2 if you’d like to test new features.

Open SidemenuDocs
Docs
Close Sidemenu

Inbound Message Signaturesinbound-message-signatures

Messaging webhooks are signed with a cryptographic signature so you can validate they came from Telnyx. Here, we will describe the signing process and how to check the signature. For how to set up the webhook and get the signature in the first place, check out the receiving messages guide.

To validate the authenticity of webhooks sent by Telnyx, compute the signature and compare. If the provided and computed signatures differ, the webhook is not authentic. Here's how to check:

Checking the Signaturechecking-the-signature

  1. 1Provide your HTTP server application with the messaging profile secret. We recommend this is configurable if you need to switch messaging profiles later, or rotate the secret if it is accidentally leaked.

Let us suppose the secret is the following. We will refer to the ASCII/UTF-8 encoded bytes below as secret

rq789onm321yxzkjihfEdcAm

  1. 2Obtain the raw byte string from the HTTP POST body. This is absolutely critical, because adding a single space, swapping two lines, or re-encoding the characters will cause the signature to dramatically change.

Here is an example payload. This sequence of bytes will be the body

{
  "sms_id":"834f3d53-8a3c-4aa0-a733-7f2d682a72df",
  "direction": "inbound",
  "from": "+13129450002",
  "to": "+13125550001",
  "body": "Hello!"
}

The precise string starts immediately before, and ends immediately after the braces, and includes 6 newline characters (there is no trailing newline). The SHA-256 hash of it begins db63cfb06... if you want to check.

  1. 3Parse the X-Telnyx-Signature header. It contains, separated by a comma:
    • t=: The timestamp when the signature was generated in Unix timeTelnyx Developers. It is in whole seconds, as a decimal number. This ASCII/byte string will be referred to below as time.
    • h=: The signature or hash, encoded as Base64Telnyx Developers. Decode this into 32 bytes. The decoded byte string will be referred to below as the signature.

For our example:

X-Telnyx-Signature: t=1520983646,h=WlEXoEsHH2RMgy2x8eyvg10JlMBco0s51fdNpMORF00=
  • time is the byte string 1520983646
  • signature is the 32 bytes that WlEX... decoded into. It begins with the ASCII letters Z, Q, a non-printable \x17, and ending with an M. This is what we'll compute independently.

Ensure that the time is within a reasonable amount of your system's time. We recommend ±30 seconds, and ensuring your system uses NTP to keep its clocks.

  1. 4Compute the signature and compare. Use HMAC with the following inputs:

The cryptographic hash function is SHA-256, i.e this is HMAC-SHA256.

The "message" is the concatenation, in order, of time, a period (. or \x2E), and the body. Following from our examples:

1520983646.{
  "sms_id":"834f3d53-8a3c-4aa0-a733-7f2d682a72df",
  "direction": "inbound",
  "from": "+13129450002",
  "to": "+13125550001",
  "body": "Hello!"
}

The secret key is the message profile's secret byte string.

Compute and compare!

Python Examplepython-example

As an example in Python

import hashlib
import hmac

# 'time', 'body', 'secret', and 'signature' are bytes specified above

message = time + b"." + body

computed_sig = hmac.HMAC(secret, message, hashlib.sha256).digest()
# b'ZQ\x17\xa0K\x07...\xc3\x91\x17M'  (32 bytes)

# constant-time comparison is ideal for security; the output is the
# same as (signature == computed_signature)
assert hmac.compare_digest(signature, computed_signature)

MMS and Moremms-and-more

When Telnyx receives an MMS intended for your phone number, the message will include an extra media field.

For instance, an MMS was sent to the same telephone number above. The payload could look like the following:

{
  "sms_id": "2c41e477-69b0-4c03-b91d-3d4a1e8f2c3b",
  "from": "+13129450002",
  "to": "+13125550001",
  "direction": "inbound",
  "body": "Hello!",
  "media": [
    {
      "url": "https://example.com/media/LONG_RANDOM_STRING.jpeg",
      "content_type": "image/jpeg",
      "hash_sha256": "sha256 hash",
      "size": 123456
    }
  ]
}

Validate the signature using the same process that is described above. The media will be available for 30 days. The URL does not require authentication; it is masked by the long unique ID. For sensitive media, take care to not inadvertently leak the URL.