Class: Basecamp::Webhooks::Receiver
- Inherits:
-
Object
- Object
- Basecamp::Webhooks::Receiver
- Defined in:
- lib/basecamp/webhooks/receiver.rb
Overview
Receives and routes webhook events from Basecamp. Framework-agnostic: works with raw body strings and a header accessor.
Constant Summary collapse
- DEFAULT_SIGNATURE_HEADER =
"X-Basecamp-Signature"- DEFAULT_DEDUP_WINDOW_SIZE =
1000
Instance Method Summary collapse
-
#handle_request(raw_body:, headers:) ⇒ Object
Process a raw webhook request.
-
#initialize(secret: nil, signature_header: DEFAULT_SIGNATURE_HEADER, dedup_window_size: DEFAULT_DEDUP_WINDOW_SIZE) ⇒ Receiver
constructor
A new instance of Receiver.
-
#on(pattern, &handler) ⇒ Object
Register a handler for a specific event kind pattern.
-
#on_any(&handler) ⇒ Object
Register a handler for all events.
-
#use(&middleware) ⇒ Object
Add middleware to the processing chain.
Constructor Details
#initialize(secret: nil, signature_header: DEFAULT_SIGNATURE_HEADER, dedup_window_size: DEFAULT_DEDUP_WINDOW_SIZE) ⇒ Receiver
Returns a new instance of Receiver.
13 14 15 16 17 18 19 20 21 22 23 24 |
# File 'lib/basecamp/webhooks/receiver.rb', line 13 def initialize(secret: nil, signature_header: DEFAULT_SIGNATURE_HEADER, dedup_window_size: DEFAULT_DEDUP_WINDOW_SIZE) @secret = secret @signature_header = signature_header @dedup_window_size = dedup_window_size @handlers = {} @any_handlers = [] @middleware = [] @dedup_seen = {} @dedup_pending = {} @dedup_order = [] @mutex = Mutex.new end |
Instance Method Details
#handle_request(raw_body:, headers:) ⇒ Object
Process a raw webhook request. Returns the parsed Event. Raises VerificationError if signature is invalid.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/basecamp/webhooks/receiver.rb', line 50 def handle_request(raw_body:, headers:) # Verify signature if @secret && !@secret.empty? signature = extract_header(headers, @signature_header) unless Verify.valid?(payload: raw_body, signature: signature, secret: @secret) raise VerificationError, "invalid webhook signature" end end # Parse event hash = JSON.parse(raw_body) event = Event.new(hash) # Atomic dedup: claim before handlers, commit on success, release on error return event unless claim(event.id) begin # Build middleware chain run_handlers = -> { dispatch_handlers(event) } chain = @middleware.reverse.reduce(run_handlers) do |next_fn, mw| -> { mw.call(event, next_fn) } end chain.call # Promote from pending to seen on success commit_seen(event.id) rescue => e # Release claim so retries can re-attempt release_claim(event.id) raise e end event end |
#on(pattern, &handler) ⇒ Object
Register a handler for a specific event kind pattern. Supports glob patterns: "todo_*" matches "todo_created", etc.
28 29 30 31 32 |
# File 'lib/basecamp/webhooks/receiver.rb', line 28 def on(pattern, &handler) @handlers[pattern] ||= [] @handlers[pattern] << handler self end |
#on_any(&handler) ⇒ Object
Register a handler for all events.
35 36 37 38 |
# File 'lib/basecamp/webhooks/receiver.rb', line 35 def on_any(&handler) @any_handlers << handler self end |
#use(&middleware) ⇒ Object
Add middleware to the processing chain. Middleware receives (event, next_proc) and must call next_proc.call to continue.
42 43 44 45 |
# File 'lib/basecamp/webhooks/receiver.rb', line 42 def use(&middleware) @middleware << middleware self end |