Module: Telnyx::Lib::WebhookVerification

Defined in:
lib/telnyx/lib/webhook_verification.rb

Overview

Telnyx Ed25519 webhook verification over the exact raw payload bytes.

Constant Summary collapse

SIGNATURE_HEADER =
"telnyx-signature-ed25519"
TIMESTAMP_HEADER =
"telnyx-timestamp"
DEFAULT_TOLERANCE =
300
ED25519_SPKI_PREFIX =
["302a300506032b6570032100"].pack("H*").freeze

Class Method Summary collapse

Class Method Details

.verify_signature!(payload, headers, public_key) ⇒ true

Verify a Telnyx webhook signature before the caller parses the payload.

Parameters:

  • payload (String)

    exact raw request body

  • headers (Hash)

    request headers (looked up case-insensitively)

  • public_key (String)

    Base64-encoded raw 32-byte Ed25519 public key

Returns:

  • (true)

Raises:



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/telnyx/lib/webhook_verification.rb', line 23

def verify_signature!(payload, headers, public_key)
  signature_header = get_header(headers, SIGNATURE_HEADER)
  timestamp = get_header(headers, TIMESTAMP_HEADER)

  fail_verification("Missing required header: telnyx-signature-ed25519") if signature_header.nil? || signature_header.empty?
  fail_verification("Missing required header: telnyx-timestamp") if timestamp.nil? || timestamp.empty?
  fail_verification("Public key is required for webhook verification") if public_key.nil? || public_key.empty?

  timestamp_int = parse_timestamp(timestamp)
  if (Time.now.to_i - timestamp_int).abs > DEFAULT_TOLERANCE
    fail_verification("Webhook timestamp is too old or too far in the future")
  end

  key = parse_public_key(public_key)
  signature = Base64.strict_decode64(signature_header)
  signed_payload = "#{timestamp}|#{payload}"

  fail_verification("Invalid webhook signature") unless key.verify(nil, signature, signed_payload)

  true
rescue Telnyx::Errors::WebhookVerificationError
  raise
rescue ArgumentError => e
  fail_verification("Invalid webhook signature or public key encoding: #{e.message}")
rescue OpenSSL::PKey::PKeyError => e
  fail_verification("Invalid Ed25519 public key: #{e.message}")
rescue OpenSSL::OpenSSLError => e
  fail_verification("Webhook verification failed: #{e.message}")
end