Skip to content

Verify signatures

Every notification is signed with your endpoint’s secret. Verifying the signature is what tells you that Hablame sent it and that nobody changed it on the way.

4 min read

How it is computed

  1. 01
    Take the time exactly as it arrives

    It is the text of the X-Hablame-Timestamp header: Unix seconds, unconverted.

  2. 02
    Build the signed message

    The time, a dot and the raw body, byte for byte: "<timestamp>.<body>". Do not parse it as JSON and write it back: a single extra space and the signature no longer matches.

  3. 03
    Compute the HMAC-SHA256

    Using your full signing secret as the key, whsec_ included. Encode it as lowercase hex and prepend sha256=.

  4. 04
    Compare in constant time

    Against X-Hablame-Signature. If it does not match and X-Hablame-Signature-Prev is present, compare that one too. If neither matches, respond 401 and do not process the notification.

Never compare with ==

A regular comparison stops at the first different character, and the time it takes tells an attacker how many they got right. Use your language’s constant-time function: timingSafeEqual, hmac.compare_digest, hash_equals, hmac.Equal, MessageDigest.isEqual or CryptographicOperations.FixedTimeEquals.

Check the time

The time is part of the signature so that a captured notification is not valid forever. Accept only notifications whose time is an integer within 300 seconds of your clock, and keep your clock in sync. A legitimate retry never arrives with an old time: the time and the signature are recalculated on every attempt, over the same body.

Test your code with this example

This notification is signed with an example secret. If your function, given that secret, that time and the exact body of the example request, produces the expected signature, your computation is correct. Skip the time check for this test: the example has a fixed date.

The same from the terminal, with OpenSSL
# It must print the same hex as the expected signature, without the sha256= prefix
printf '%s' '1792854247.{"id":"0192f0c47b1e7a3d9c553e2a1b4c5d6e","type":"sms.mo.87654","data":{"shortCode":"87654","from":"573001234567","text":"Hola, quiero más información","receivedAt":"2026-10-24T15:04:05Z","messageId":"0192f0c4-7b1e-7a3d-9c55-3e2a1b4c5d6e"}}' \
  | openssl dgst -sha256 -hmac 'whsec_TU_SECRETO_DE_FIRMA'

When the secret is rotated

During the 24 hours after a rotation, each notification carries two signatures with the same time and body: X-Hablame-Signature with the new secret and X-Hablame-Signature-Prev with the previous one. If your code compares against both, you can switch the secret in your system at any point in that window without rejecting a single notification. After the window, only the new signature arrives.

Common mistakes

Do this
Instead of this
Sign the raw body, exactly as it arrived.
Letting your web framework parse the JSON and re-serializing it before signing.
Use the full secret as the key, with whsec_.
Stripping the whsec_ prefix from the secret.
Compare in constant time.
Comparing with == or equals.
Also accept X-Hablame-Signature-Prev.
Rejecting notifications during a rotation because you only check the main signature.

Need help?

Was this page helpful?

See all support channels

Your cookie preferences

You decide what we measure. You can change your choice at any time. Rejecting takes no feature away from the documentation.

Privacy policy