How do I verify SMS Webhooks?


#1

I am curious as to how this is done via the Telnyx platform. Can someone help answer my question?


#2

Great question!

First a little from the docs:

Telnyx signs webhooks it sends to your endpoint, allowing you to validate that they were not sent by a third-party. The HTTP request includes the header X-Telnyx-Signature. The X-Telnyx-Signature header contains an epoch timestamp in seconds and a signature. The timestamp is prefixed by t=, and the signature is prefixed by h=.

So the first step is to extract the timestamp and signature values from the X-Telnyx-Signature header. As an example I’ll include some Ruby code from a Ruby on Rails project.

signature_hash = request
                 .headers["X-Telnyx-Signature"]
                 .strip
                 .split(",")
                 .map { |str| str.split("=", 2) }
                 .to_h
# Output will be something like {"h" => "hash_value", "t" => "1522434831"}

The next step is to use these values to check the authenticity of the webhook.

First build a string by concatenating the timestamp value, a period . and the body of the webhook. Again in Ruby this would look like

data = "#{signature_hash['t']}.#{request.raw_post}"

Then build the HMAC digest (signature) of the data using your Telnyx messaging profile secret as the key. This is a value only you and Telnyx know.

key = ENV["TELNYX_MESSAGING_PROFILE_SECRET"] #load your messaging profile secret from an environment variable
signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), key, data)).strip()

Now you just need to compare the value of the signature you have generated with that of the signature sent in the request headers.

signature == signature_hash["h"]

If these values are the same then you know that the request was generated by Telnyx as only you and Telnyx know the value of your messaging profile secret.

So what is the timestamp for? Well it provides you an easy way to verify that the request is not being replayed after being intercepted in transit. The test for this is simple.

now = Time.now.utc.to_i
(now - signature_hash["t"].to_i) < 2 # the number of seconds for which you consider a request valid

If the result is true then the request left the Telnyx servers less than 2 seconds ago.