signwell_sdk 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/examples/09_webhook_validation.rb +10 -4
- data/lib/signwell_sdk/version.rb +1 -1
- data/lib/signwell_sdk/webhook.rb +49 -15
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 55bbaffc014507a2d8aac8a98b5050e8283576e119595023bcdc80de464a3d10
|
|
4
|
+
data.tar.gz: 2f3338dfec89e4806bffa9fc1bf5ad5004c72e5fff5b6b99dd843cdaac38d139
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 67288c6373cb34e4f1ac755f5b2ceb94468aabb32112a7637407c759c6dc1088d35b8d6cce81884ab3d244328822fad089828a321f13d40001a7efbf05fc3d7d
|
|
7
|
+
data.tar.gz: 58db67c107fade9f556042632aea5a3b22c39ff94ce811bd1293fbfc2469c36cf293d84b377236fe5f435eccbfb5f24ccfcd5c4ded112f09dd2a8e55fa1830aa
|
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
# Webhook Signature Validation
|
|
4
4
|
#
|
|
5
5
|
# When SignWell sends a webhook to your endpoint, each payload includes
|
|
6
|
-
#
|
|
6
|
+
# an `event.hash` field with an HMAC-SHA256 signature. Use SignWell::Webhook.verify_event
|
|
7
7
|
# to validate the signature before processing the event.
|
|
8
8
|
#
|
|
9
|
+
# The webhook payload has this structure:
|
|
10
|
+
# { "event": { "type": "...", "time": ..., "hash": "..." }, "data": { ... } }
|
|
11
|
+
#
|
|
12
|
+
# You must pass `payload['event']` (not the full payload) to verify_event.
|
|
13
|
+
#
|
|
9
14
|
# Your webhook ID is available in the SignWell dashboard (Settings > Webhooks).
|
|
10
15
|
|
|
11
16
|
require 'signwell_sdk'
|
|
@@ -13,7 +18,8 @@ require 'sinatra'
|
|
|
13
18
|
require 'json'
|
|
14
19
|
|
|
15
20
|
post '/webhooks/signwell' do
|
|
16
|
-
|
|
21
|
+
payload = JSON.parse(request.body.read)
|
|
22
|
+
event = payload['event']
|
|
17
23
|
|
|
18
24
|
unless SignWell::Webhook.verify_event(event: event, webhook_id: ENV['SIGNWELL_WEBHOOK_ID'])
|
|
19
25
|
halt 401, 'Invalid signature'
|
|
@@ -21,9 +27,9 @@ post '/webhooks/signwell' do
|
|
|
21
27
|
|
|
22
28
|
case event['type']
|
|
23
29
|
when 'document_completed'
|
|
24
|
-
puts "Document #{
|
|
30
|
+
puts "Document #{payload.dig('data', 'id')} completed"
|
|
25
31
|
when 'document_declined'
|
|
26
|
-
puts "Document #{
|
|
32
|
+
puts "Document #{payload.dig('data', 'id')} declined"
|
|
27
33
|
end
|
|
28
34
|
|
|
29
35
|
status 200
|
data/lib/signwell_sdk/version.rb
CHANGED
data/lib/signwell_sdk/webhook.rb
CHANGED
|
@@ -10,17 +10,15 @@ module SignWell
|
|
|
10
10
|
# computed from the event type and timestamp, using your webhook's secret ID
|
|
11
11
|
# as the key.
|
|
12
12
|
#
|
|
13
|
-
# @example Verify a webhook in a Rails controller
|
|
13
|
+
# @example Verify a webhook in a Rails controller (using verify_event!)
|
|
14
14
|
# class WebhooksController < ApplicationController
|
|
15
15
|
# skip_before_action :verify_authenticity_token
|
|
16
16
|
#
|
|
17
17
|
# def create
|
|
18
|
-
#
|
|
18
|
+
# payload = JSON.parse(request.body.read)
|
|
19
|
+
# event = payload['event']
|
|
19
20
|
#
|
|
20
|
-
#
|
|
21
|
-
# head :unauthorized
|
|
22
|
-
# return
|
|
23
|
-
# end
|
|
21
|
+
# SignWell::Webhook.verify_event!(event: event, webhook_id: ENV['SIGNWELL_WEBHOOK_ID'])
|
|
24
22
|
#
|
|
25
23
|
# # Process the verified event
|
|
26
24
|
# case event['type']
|
|
@@ -29,12 +27,15 @@ module SignWell
|
|
|
29
27
|
# end
|
|
30
28
|
#
|
|
31
29
|
# head :ok
|
|
30
|
+
# rescue ArgumentError
|
|
31
|
+
# head :unauthorized
|
|
32
32
|
# end
|
|
33
33
|
# end
|
|
34
34
|
#
|
|
35
|
-
# @example Verify a webhook in a Sinatra app
|
|
35
|
+
# @example Verify a webhook in a Sinatra app (using verify_event)
|
|
36
36
|
# post '/webhooks/signwell' do
|
|
37
|
-
#
|
|
37
|
+
# payload = JSON.parse(request.body.read)
|
|
38
|
+
# event = payload['event']
|
|
38
39
|
#
|
|
39
40
|
# halt 401 unless SignWell::Webhook.verify_event(event: event, webhook_id: ENV['SIGNWELL_WEBHOOK_ID'])
|
|
40
41
|
#
|
|
@@ -45,23 +46,56 @@ module SignWell
|
|
|
45
46
|
# @see https://developers.signwell.com/reference/webhooks SignWell Webhooks Documentation
|
|
46
47
|
module Webhook
|
|
47
48
|
# Verifies the authenticity of a SignWell webhook event using HMAC-SHA256.
|
|
49
|
+
# Returns +false+ for missing or invalid arguments instead of raising.
|
|
50
|
+
#
|
|
51
|
+
# @param event [Hash] The +event+ object from the webhook payload.
|
|
52
|
+
# Must contain +'type'+, +'time'+, and +'hash'+ keys.
|
|
53
|
+
# @param webhook_id [String] Your webhook's secret ID.
|
|
54
|
+
# @return [Boolean] +true+ if the signature is valid, +false+ otherwise
|
|
55
|
+
# @see #verify_event! Bang version that raises on invalid input
|
|
56
|
+
def self.verify_event(event:, webhook_id:)
|
|
57
|
+
verify_event!(event: event, webhook_id: webhook_id)
|
|
58
|
+
rescue ArgumentError
|
|
59
|
+
false
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Verifies the authenticity of a SignWell webhook event using HMAC-SHA256.
|
|
63
|
+
# Raises +ArgumentError+ with a descriptive message when input is invalid.
|
|
48
64
|
#
|
|
49
65
|
# Computes +HMAC-SHA256(webhook_id, "type@time")+ and compares it to the
|
|
50
66
|
# +hash+ field in the event payload using a constant-time comparison.
|
|
51
67
|
#
|
|
52
|
-
# @param event [Hash]
|
|
68
|
+
# @param event [Hash] The +event+ object from the webhook payload.
|
|
69
|
+
# Must contain:
|
|
53
70
|
# - +'type'+ (String) - the event type, e.g. +"document_completed"+
|
|
54
|
-
# - +'time'+ (String) -
|
|
71
|
+
# - +'time'+ (String, Integer) - timestamp of the event
|
|
55
72
|
# - +'hash'+ (String) - HMAC-SHA256 hex digest to verify against
|
|
56
73
|
# @param webhook_id [String] Your webhook's secret ID (found in your
|
|
57
74
|
# SignWell dashboard under API > Webhooks). Used as the HMAC key.
|
|
58
75
|
# @return [Boolean] +true+ if the signature is valid, +false+ otherwise
|
|
59
|
-
# @raise [
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
76
|
+
# @raise [ArgumentError] if +webhook_id+ is missing or +event+ is missing
|
|
77
|
+
# required keys
|
|
78
|
+
def self.verify_event!(event:, webhook_id:)
|
|
79
|
+
raise ArgumentError, 'webhook_id must be a non-empty string' unless webhook_id.is_a?(String) && !webhook_id.empty?
|
|
80
|
+
raise ArgumentError, 'event must be a Hash' unless event.is_a?(Hash)
|
|
81
|
+
|
|
82
|
+
type = event['type']
|
|
83
|
+
time = event['time']
|
|
84
|
+
hash = event['hash']
|
|
85
|
+
|
|
86
|
+
missing = []
|
|
87
|
+
missing << 'type' unless type
|
|
88
|
+
missing << 'time' unless time
|
|
89
|
+
missing << 'hash' unless hash
|
|
90
|
+
unless missing.empty?
|
|
91
|
+
raise ArgumentError,
|
|
92
|
+
"event is missing required keys: #{missing.join(', ')}. " \
|
|
93
|
+
'Make sure you pass payload["event"], not the full webhook payload'
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
data = "#{type}@#{time}"
|
|
63
97
|
calculated = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('SHA256'), webhook_id, data)
|
|
64
|
-
secure_compare(calculated,
|
|
98
|
+
secure_compare(calculated, hash)
|
|
65
99
|
end
|
|
66
100
|
|
|
67
101
|
# @api private
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: signwell_sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- SignWell
|
|
@@ -413,7 +413,7 @@ homepage: https://github.com/Bidsketch/signwell-sdk-ruby
|
|
|
413
413
|
licenses:
|
|
414
414
|
- MIT
|
|
415
415
|
metadata:
|
|
416
|
-
documentation_uri: https://gemdocs.org/gems/signwell_sdk/0.1.
|
|
416
|
+
documentation_uri: https://gemdocs.org/gems/signwell_sdk/0.1.1/
|
|
417
417
|
source_code_uri: https://github.com/Bidsketch/signwell-sdk-ruby
|
|
418
418
|
homepage_uri: https://github.com/Bidsketch/signwell-sdk-ruby
|
|
419
419
|
changelog_uri: https://github.com/Bidsketch/signwell-sdk-ruby/blob/main/CHANGELOG.md
|