Module: HighLevel::Webhooks

Defined in:
lib/high_level/webhooks.rb

Overview

Verifies inbound HighLevel webhook signatures.

HighLevel signs webhook payloads with the platform’s private key and publishes the corresponding public key. Verification is therefore asymmetric — the ‘public_key:` argument is the published PEM key, not a shared secret.

Two schemes are supported:

:rsa     — RSA-SHA256. Signature arrives on `x-wh-signature`.
:ed25519 — Ed25519.    Signature arrives on `x-ghl-signature`.

Both signatures are base64-encoded. The payload is the raw JSON body of the request as bytes — do not re-serialize via your framework’s parser (round-tripping changes the canonical form).

Source: vendor/highlevel-api-sdk/lib/webhook/webhook-manager.ts (verifySignature + verifyEd25519Signature) cross-checked with the published HighLevel webhook docs.

Defined Under Namespace

Classes: InvalidSignatureError

Constant Summary collapse

SCHEMES =

The supported signature schemes.

%i[rsa ed25519].freeze

Class Method Summary collapse

Class Method Details

.verify(payload:, signature:, public_key:, scheme: :rsa) ⇒ Object

Verify a webhook signature. Returns true on success; raises InvalidSignatureError on any failure mode (bad signature, wrong key, malformed base64, missing inputs). Caller-supplied ArgumentError for invalid scheme. rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity



38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/high_level/webhooks.rb', line 38

def self.verify(payload:, signature:, public_key:, scheme: :rsa)
  raise ArgumentError, "scheme must be one of: #{SCHEMES.join(", ")}" unless SCHEMES.include?(scheme)
  raise InvalidSignatureError, "signature is required" if signature.nil? || signature.to_s.empty?
  raise InvalidSignatureError, "public_key is required" if public_key.nil? || public_key.to_s.empty?

  signature_bytes = decode_signature(signature)
  key = load_key(public_key)
  valid = verify_for(scheme, key, signature_bytes, payload.to_s)
  raise InvalidSignatureError, "signature does not match payload" unless valid

  true
rescue OpenSSL::OpenSSLError => e
  raise InvalidSignatureError, "openssl error: #{e.message}"
end