Inbound 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 Signature
- Provide 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
- Obtain 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!"
}
Note: After pasting the above content, Kindly check and remove any new line added
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.
-
Parse the
X-Telnyx-Signature
header. It contains, separated by a comma:-
t=
: The timestamp when the signature was generated in Unix time . It is in whole seconds, as a decimal number. This ASCII/ byte string will be referred to below astime
. -
h=
: The signature or hash, encoded as Base64 . Decode this into 32 bytes. The decoded byte string will be referred to below as thesignature
.
-
For our example:
X-Telnyx-Signature: t=1520983646,h=WlEXoEsHH2RMgy2x8eyvg10JlMBco0s51fdNpMORF00=
Note: After pasting the above content, Kindly check and remove any new line added
-
time
is the byte string1520983646
-
signature
is the 32 bytes thatWlEX...
decoded into. It begins with the ASCII lettersZ
,Q
, a non-printable\x17
, and ending with anM
. 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.
- Compute 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!"
}
Note: After pasting the above content, Kindly check and remove any new line added
The secret key is the message profile's secret
byte string.
Compute and compare!
Python 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)
Note: After pasting the above content, Kindly check and remove any new line added
MMS 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
}
]
}
Note: After pasting the above content, Kindly check and remove any new line added
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.