Verifying webhook signatures

Verify the events that Capable Health sends to your webhook endpoints.

Capable Health automatically signs the webhook events it sends to your endpoints by including a signature in each event's Capable-Signature header. This allows you to verify that the events were sent by Capable Health.

Before verifying signatures, you need to retrieve your tenant webhook signature key secret from your portal Credentials page. Navigate to the Webhook Signature Keys section, and click the reveal button.

Capable Health generates a unique secret key common to every webhooks, but note that the secret is different for each environment.

Verify the signature manually

The Capable-Signature header included in each signed event contains a timestamp and one or more signatures. The timestamp is prefixed by t=, and each signature is prefixed by s=.

example: Capable-Signature: "t=1663339507, s=0533...01dd83d8a

It is possible to have multiple signatures. This can happen when we help you roll out your secret. The previous secret will stay active until we remove it. During this time, Capable generates one signature for each secret.

example: Capable-Signature: "t=1663339507, s=053...3d8a, s=jd0...48d

Steps to verify the signature

Step 1: Extract the timestamp and signatures from the header

  • Split the header, using the , character as the separator, to get a list of elements.
  • Then split each element, using the = character as the separator, to get a prefix and value pair.

The value for the prefix t corresponds to the timestamp, and s corresponds to the signature(s).

Step 2: Prepare your signed payload string

The data used for the signature is created by concatenating:

  • The timestamp (as a string)
  • The character .
  • The raw JSON payload (that is, the raw body of the request)

Step 3: Determine the expected signature

Compute an HMAC with the SHA256 hash function. Use your webhook signature key secret as the key and the signed payload string (from step 2) as the data.

Step 4: Compare the signatures

Compare the result from step 3, the signature you recreated, with the received signature in the Capable-Signature header.

Once you know the signature is valid, in addition (not in replacement), you may also compute the difference between the current timestamp and the received timestamp and decide if the difference is within your tolerance.

Examples

Ruby

signature_header = request.headers["Capable-Signature"]
# $> "t=1663280523, s=0533c61748...ef6b83d8a, s=ef6...33c6"

# Retrieve data from the headers
parts = signature_header.split(", ")
parts = parts.map { |part| part.split("=")}
timestamp = nil
signatures = [] # multiple signatures are possible.
                # One signature per available CH webhook key.
parts.each do |part|
  timestamp = value if key == "t"
  signatures << value if key == "s"
end

# Recreate the signature
data = timestamp + "." + request.raw_body
key = ENV.fetch("CH_WEBHOOK_SIGNATURE_KEY")
digest = OpenSSL::Digest.new("sha256")
recreated_signature = OpenSSL::HMAC.hexdigest(digest, key, data)

# Check validity
is_valid = signatures.include?(recreated_signature)
is_valid &&= timestamp < Time.current + 10.seconds