Verifying Webhook Signature
Verify the events that Tiltify sends to your Webhook Endpoints.
Tiltify will sign every Webhook request with a signature found in the
X-Tiltify-Signature
header. By verifying this signature you can verify that
the events were sent by Tiltify, and not a malicious third party.
Manual Verification
You can manaually verify the signature by following these steps.
All of the examples on this page use real values, so you can test your verification locally.
1. Extract the Signature
This is found in the X-Tiltify-Signature
header. This is base64 encoded, and
you will need to decode it to compare signatures.
Example Signature:
4OSwlhTt0EcrlSQFlqgE18FOtT+EKX4qTJdJeC8oV/o=
2. Extract the Timestamp
This is found in the X-Tiltify-Timestamp
header. This is an ISO-8601 encoded
timestamp of when the Webhook Attempt was made. It should be recent to the
current timestamp (within the last minute).
Example Timestamp:
2023-04-18T16:49:00.617031Z
3. Extract the Request Body
This is the JSON request body of the POST request to your endpoint.
Example Request Body:
{"data":{"amount":{"currency":"USD","value":"82.95"},"campaign_id":"a4fd5207-bd9f-4712-920a-85f8d92cf4e6","completed_at":"2023-04-18T16:48:26.510702Z","created_at":"2023-04-18T03:36:36.510717Z","donor_comment":"Rerum quo necessitatibus voluptas provident ad molestiae ipsam.","donor_name":"Jirachi","fundraising_event_id":null,"id":"dfa25dcc-2026-4320-a5b7-5da076efeb05","legacy_id":0,"poll_id":null,"poll_option_id":null,"reward_id":null,"sustained":false,"target_id":null,"team_event_id":null},"meta":{"attempted_at":"2023-04-18T16:49:00.617031Z","event_type":"public:direct:donation_updated","generated_at":"2023-04-18T16:48:59.510758Z","id":"d8768e26-1092-4f4c-a829-a2698cd19664","subscription_source_id":"00000000-0000-0000-0000-000000000000","subscription_source_type":"test"}}
You will need the raw string that we send in the request body, do not parse and regenerate the string, as this can change with the spacing and ordering of the string.
4. Generate Signed Payload
This is a string that contains the following parts:
- The extracted Timestamp in ISO-8601 format
- The character
.
- The extracted Request Body as a raw string
Signed Payload Format:
"#{timestamp}.#{request_body}"
Example Signed Payload
2023-04-18T16:49:00.617031Z.{"data":{"amount":{"currency":"USD","value":"82.95"},"campaign_id":"a4fd5207-bd9f-4712-920a-85f8d92cf4e6","completed_at":"2023-04-18T16:48:26.510702Z","created_at":"2023-04-18T03:36:36.510717Z","donor_comment":"Rerum quo necessitatibus voluptas provident ad molestiae ipsam.","donor_name":"Jirachi","fundraising_event_id":null,"id":"dfa25dcc-2026-4320-a5b7-5da076efeb05","legacy_id":0,"poll_id":null,"poll_option_id":null,"reward_id":null,"sustained":false,"target_id":null,"team_event_id":null},"meta":{"attempted_at":"2023-04-18T16:49:00.617031Z","event_type":"public:direct:donation_updated","generated_at":"2023-04-18T16:48:59.510758Z","id":"d8768e26-1092-4f4c-a829-a2698cd19664","subscription_source_id":"00000000-0000-0000-0000-000000000000","subscription_source_type":"test"}}
5. Determine the expected signature
Compute an HMAC with the SHA256 function using your Secret Webhook Signing Key found on the Webhook Endpoint's Overview page. Then you will base 64 encode the resulting signature.
Example Expected Signature:
4OSwlhTt0EcrlSQFlqgE18FOtT+EKX4qTJdJeC8oV/o=
6. Compare the Signatures
Now compare the X-Tiltify-Signature with your base 64 encoded expected signature. If they are equal the webhook is verified as coming from Tiltify.
Example Verification
This will show a complete example verification using the values found in this guide:
Secret Webhook Signing Key
13c3b68914487acd1c68d85857ee1cfc308f15510f2d8e71273ee0f8a42d9d00
X-Tiltify-Signature
4OSwlhTt0EcrlSQFlqgE18FOtT+EKX4qTJdJeC8oV/o=
X-Tiltify-Timestamp
2023-04-18T16:49:00.617031Z
Request Body
{"data":{"amount":{"currency":"USD","value":"82.95"},"campaign_id":"a4fd5207-bd9f-4712-920a-85f8d92cf4e6","completed_at":"2023-04-18T16:48:26.510702Z","created_at":"2023-04-18T03:36:36.510717Z","donor_comment":"Rerum quo necessitatibus voluptas provident ad molestiae ipsam.","donor_name":"Jirachi","fundraising_event_id":null,"id":"dfa25dcc-2026-4320-a5b7-5da076efeb05","legacy_id":0,"poll_id":null,"poll_option_id":null,"reward_id":null,"sustained":false,"target_id":null,"team_event_id":null},"meta":{"attempted_at":"2023-04-18T16:49:00.617031Z","event_type":"public:direct:donation_updated","generated_at":"2023-04-18T16:48:59.510758Z","id":"d8768e26-1092-4f4c-a829-a2698cd19664","subscription_source_id":"00000000-0000-0000-0000-000000000000","subscription_source_type":"test"}}
- Elixir
- Node.js
- PHP
@doc """
Verifies a Tiltify Webhook Signature is valid
"""
@spec verify_signature(
secret :: String.t(),
signature :: String.t(),
timestamp :: String.t(),
body :: String.t()
) :: boolean()
def verify_signature(secret, signature, timestamp, body) do
:hmac
|> :crypto.mac(:sha256, secret, "#{timestamp}.#{body}")
|> Base.encode64()
|> Kernel.==(signature)
end
import * as crypto from 'node:crypto'
/**
* Verifies a Tiltify Webhook Signature is valid
*
* @param {string} secret
* @param {string} signature
* @param {string} timestamp
* @param {string} body
* @returns {boolean}
*/
const verifySignature = (secret, signature, timestamp, body) => {
const hmac = crypto.createHmac('sha256', secret)
hmac.update(`${timestamp}.${body}`)
return signature === hmac.digest('base64')
}
/**
* Verifies a Tiltify Webhook Signature is valid
*
* @param string $secret
* @param string $signature
* @param string $timestamp
* @param string $body
* @return bool
*/
function verify_signature($secret, $signature, $timestamp, $body) {
$hmac = hash_hmac('sha256', "$timestamp.$body", $secret, $raw_output = true);
$expected = base64_encode($hmac);
return $signature === $expected;
};