Module: EasyDocForms::Webhook
- Defined in:
- lib/easydocforms/webhook.rb
Overview
Verifies webhook deliveries against a subscription's whsec_* secret (returned once by Client#create_webhook).
Every delivery is signed:
X-EDF-Signature: t=<unix>,v1=<hex hmac-sha256(secret, "<t>.<raw body>")>
Verify over the RAW request body, before any JSON parsing:
event = EasyDocForms::Webhook.construct_event(
payload: request.body.read,
header: request.headers["X-EDF-Signature"],
secret: ENV["EASYDOCFORMS_WEBHOOK_SECRET"]
)
case event[:event]
when "submission.created" then ... # event[:data][:submission_id]
end
Constant Summary collapse
- HEADER =
"X-EDF-Signature"- DEFAULT_TOLERANCE =
seconds
300
Class Method Summary collapse
-
.construct_event(payload:, header:, secret:, tolerance: DEFAULT_TOLERANCE, now: Time.now) ⇒ Object
Verifies a delivery, then parses and returns its envelope as a Hash with symbol keys: { event:, timestamp:, org_id:, data: }.
-
.sign(payload:, secret:, timestamp: Time.now.to_i) ⇒ Object
Computes an X-EDF-Signature header value — for building authentic fixtures in your own tests.
-
.verify!(payload:, header:, secret:, tolerance: DEFAULT_TOLERANCE, now: Time.now) ⇒ Object
Verifies a delivery's signature and timestamp.
Class Method Details
.construct_event(payload:, header:, secret:, tolerance: DEFAULT_TOLERANCE, now: Time.now) ⇒ Object
Verifies a delivery, then parses and returns its envelope as a Hash with symbol keys: { event:, timestamp:, org_id:, data: }.
46 47 48 49 |
# File 'lib/easydocforms/webhook.rb', line 46 def self.construct_event(payload:, header:, secret:, tolerance: DEFAULT_TOLERANCE, now: Time.now) verify!(payload: payload, header: header, secret: secret, tolerance: tolerance, now: now) JSON.parse(payload, symbolize_names: true) end |
.sign(payload:, secret:, timestamp: Time.now.to_i) ⇒ Object
Computes an X-EDF-Signature header value — for building authentic fixtures in your own tests.
53 54 55 |
# File 'lib/easydocforms/webhook.rb', line 53 def self.sign(payload:, secret:, timestamp: Time.now.to_i) "t=#{},v1=#{OpenSSL::HMAC.hexdigest("SHA256", secret, "#{}.#{payload}")}" end |
.verify!(payload:, header:, secret:, tolerance: DEFAULT_TOLERANCE, now: Time.now) ⇒ Object
Verifies a delivery's signature and timestamp. Returns true, or raises
EasyDocForms::SignatureVerificationError. payload must be the raw
request body string; tolerance bounds |now - t| (replay window).
31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/easydocforms/webhook.rb', line 31 def self.verify!(payload:, header:, secret:, tolerance: DEFAULT_TOLERANCE, now: Time.now) , signatures = parse_header(header) expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{}.#{payload}") unless signatures.any? { |signature| secure_compare(signature, expected) } raise SignatureVerificationError, "no v1 signature matches the payload" end if (now.to_i - ).abs > tolerance raise SignatureVerificationError, "timestamp outside the #{tolerance}s tolerance" end true end |