rerout 0.2.0 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 78cf1ae4573eccdeed6874c837565ea69f415c928c23def1fe506e674be53ec3
4
- data.tar.gz: aa23cc62bcd1c3f2f80ee460fe691d0241027020e5d7f5d3bfacb2e909263286
3
+ metadata.gz: 6f212d64e3f65e342bc6034aa992c270a2322534dc127e18fa4b7defbd6e0b08
4
+ data.tar.gz: bb2fd2083df69c90e884769fe5aad80b40d11dde7ba7696d96bc04ac73393833
5
5
  SHA512:
6
- metadata.gz: 4f1e411e4301abfd0009626920e3e974ca4f2b7db05de0d930e2eb9d432b3d71e8c51b5185875c04fdc8edd97cf1cc756e93215cc24e82103086f475714732e0
7
- data.tar.gz: 1722814f198aca44eded94a7f5c983a7f30a9437a75652046ad61226cd454d005b248fe500a68080c4aa5b98fc0089274fe610089a3ac8f5b2a68832fcfb723b
6
+ metadata.gz: a86ed8c18689fa52099e11a1fc1bedec3ddd2aa5897ff2d9c694290260892712591bccc61b1aee0129e24e7f2182b5d5dc7657126dfb9960e9cfb6ad3229b79d
7
+ data.tar.gz: 812e84cfb86041e00b6798bd149176818162d7c25f56267dc1be12a7a7c4f5c1c8e816a0744194c0b9ce752619bc090deef52f7742a594a8f6e3ae77f55feb07
data/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ All notable changes to the `rerout` gem are documented in this file. The
4
4
  format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.3.0] - 2026-06-03
8
+
9
+ ### Added
10
+
11
+ - Webhook endpoint management via a new `webhooks` namespace — `create`, `list`,
12
+ and `delete` against `/v1/projects/me/webhooks` (API-key auth). The signing
13
+ secret returned by `create` is shown once.
14
+
7
15
  ## [0.2.0] - 2026-06-02
8
16
 
9
17
  ### Added
@@ -40,5 +48,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
40
48
  - `Rerout::Error` with stable `code`, `status`, `path`, `timestamp`, `details`
41
49
  plus `rate_limited?` and `server_error?` convenience flags.
42
50
 
51
+ [0.3.0]: https://github.com/ModestNerds-Co/rerout-sdks/releases/tag/ruby/v0.3.0
43
52
  [0.2.0]: https://github.com/ModestNerds-Co/rerout-sdks/releases/tag/ruby-v0.2.0
44
53
  [0.1.0]: https://github.com/ModestNerds-Co/rerout-sdks/releases/tag/ruby-v0.1.0
data/README.md CHANGED
@@ -64,7 +64,7 @@ rerout = Rerout::Client.new(
64
64
  A blank or missing `api_key` raises `Rerout::Error` with code `missing_api_key`
65
65
  before any network call.
66
66
 
67
- The client exposes three namespaces: `links`, `project`, and `qr`.
67
+ The client exposes four namespaces: `links`, `project`, `qr`, and `webhooks`.
68
68
 
69
69
  ## Links
70
70
 
@@ -147,6 +147,38 @@ File.write('q4.svg', svg)
147
147
  QR options: `size` (1–32), `margin` (0–16), `ecc` (`L`/`M`/`Q`/`H`), `domain`,
148
148
  and `refresh` (`true` is serialized as `1`; a string is sent verbatim).
149
149
 
150
+ ## Webhook management
151
+
152
+ Manage the webhook endpoints that receive event deliveries for the project that
153
+ owns the API key. (This is the `webhooks` namespace — distinct from
154
+ `Rerout::Webhooks`, which verifies inbound signatures below.)
155
+
156
+ ```ruby
157
+ # Create — name, url, and events are required.
158
+ created = rerout.webhooks.create(
159
+ Rerout::CreateWebhookInput.new(
160
+ name: 'prod listener',
161
+ url: 'https://hooks.brand.com/rerout',
162
+ events: ['link.created'],
163
+ payload_format: 'json' # optional: "json" (default) or "slack"
164
+ )
165
+ )
166
+
167
+ created.endpoint.id # => "wh_..."
168
+ created.signing_secret # => "whsec_..." — shown ONCE; persist it now.
169
+
170
+ # List endpoints plus the event types the server can deliver.
171
+ result = rerout.webhooks.list
172
+ result.endpoints # => [Rerout::Models::Webhook, ...]
173
+ result.event_types # => ["link.created", ...]
174
+
175
+ # Delete (soft delete) — idempotent.
176
+ rerout.webhooks.delete('wh_...') # => { "deleted" => true }
177
+ ```
178
+
179
+ The `signing_secret` returned by `create` is the value you pass as `secret:` to
180
+ `verify_signature` below — store it securely, as it cannot be retrieved again.
181
+
150
182
  ## Webhook signature verification
151
183
 
152
184
  Rerout signs every webhook delivery with an `X-Rerout-Signature` header. Verify
data/lib/rerout/client.rb CHANGED
@@ -7,12 +7,14 @@ require_relative 'version'
7
7
  require_relative 'error'
8
8
  require_relative 'create_link_input'
9
9
  require_relative 'update_link_input'
10
+ require_relative 'create_webhook_input'
10
11
  require_relative 'qr_options'
11
12
  require_relative 'webhooks'
12
13
  require_relative 'models'
13
14
  require_relative 'links'
14
15
  require_relative 'project'
15
16
  require_relative 'qr'
17
+ require_relative 'webhooks_resource'
16
18
 
17
19
  module Rerout
18
20
  # Default production API base URL.
@@ -36,6 +38,8 @@ module Rerout
36
38
  attr_reader :project
37
39
  # @return [Resources::Qr] QR namespace.
38
40
  attr_reader :qr
41
+ # @return [Resources::Webhooks] webhook endpoint management namespace.
42
+ attr_reader :webhooks
39
43
 
40
44
  # @param api_key [String] project API key (`rrk_…`). Required.
41
45
  # @param base_url [String, nil] override base URL. Defaults to `https://api.rerout.co`.
@@ -61,6 +65,7 @@ module Rerout
61
65
  @links = Resources::Links.new(self)
62
66
  @project = Resources::Project.new(self)
63
67
  @qr = Resources::Qr.new(self)
68
+ @webhooks = Resources::Webhooks.new(self)
64
69
  end
65
70
 
66
71
  # Perform a JSON request against the Rerout API.
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rerout
4
+ # Request body for `POST /v1/projects/me/webhooks`. `name`, `url`, and
5
+ # `events` are required; `is_active` and `payload_format` are optional and
6
+ # omitted from the payload when not set (server defaults apply).
7
+ class CreateWebhookInput
8
+ attr_reader :name, :url, :events, :is_active, :payload_format
9
+
10
+ # @param name [String] required, human-readable label for the endpoint.
11
+ # @param url [String] required, public https:// URL that receives deliveries.
12
+ # @param events [Array<String>] required, non-empty list of event types
13
+ # to subscribe to (e.g. `link.created`).
14
+ # @param is_active [Boolean, nil] whether the endpoint starts active.
15
+ # Server default: `true`.
16
+ # @param payload_format [String, nil] payload encoding — `"json"` or
17
+ # `"slack"`. Server default: `"json"`.
18
+ def initialize(name:, url:, events:, is_active: nil, payload_format: nil)
19
+ raise ArgumentError, 'name is required' if name.nil? || name.to_s.empty?
20
+ raise ArgumentError, 'url is required' if url.nil? || url.to_s.empty?
21
+ if events.nil? || !events.is_a?(Array) || events.empty?
22
+ raise ArgumentError, 'events is required and must be a non-empty Array'
23
+ end
24
+
25
+ @name = name
26
+ @url = url
27
+ @events = events
28
+ @is_active = is_active
29
+ @payload_format = payload_format
30
+ freeze
31
+ end
32
+
33
+ # Serialize for the wire. Optional fields are only included when set.
34
+ def to_h
35
+ hash = { 'name' => name, 'url' => url, 'events' => events }
36
+ hash['is_active'] = is_active unless is_active.nil?
37
+ hash['payload_format'] = payload_format unless payload_format.nil?
38
+ hash
39
+ end
40
+ end
41
+ end
data/lib/rerout/models.rb CHANGED
@@ -301,5 +301,115 @@ module Rerout
301
301
  [self.class, id, name, slug].hash
302
302
  end
303
303
  end
304
+
305
+ # A webhook endpoint registered to the project. Mirrors the server-side
306
+ # `WebhookEndpointResponse`.
307
+ class Webhook
308
+ ATTRS = %i[
309
+ id project_id name url events is_active payload_format
310
+ created_at updated_at last_delivery_at last_success_at last_failure_at
311
+ ].freeze
312
+
313
+ attr_reader(*ATTRS)
314
+
315
+ def initialize(**attrs)
316
+ ATTRS.each { |k| instance_variable_set(:"@#{k}", attrs[k]) }
317
+ @events = (@events || []).freeze
318
+ freeze
319
+ end
320
+
321
+ def self.from_hash(hash)
322
+ new(
323
+ id: hash['id'],
324
+ project_id: hash['project_id'],
325
+ name: hash['name'],
326
+ url: hash['url'],
327
+ events: hash['events'] || [],
328
+ is_active: hash['is_active'],
329
+ payload_format: hash['payload_format'],
330
+ created_at: hash['created_at'],
331
+ updated_at: hash['updated_at'],
332
+ last_delivery_at: hash['last_delivery_at'],
333
+ last_success_at: hash['last_success_at'],
334
+ last_failure_at: hash['last_failure_at']
335
+ )
336
+ end
337
+
338
+ def to_h
339
+ ATTRS.to_h { |k| [k, public_send(k)] }
340
+ end
341
+
342
+ def ==(other)
343
+ other.is_a?(Webhook) && other.to_h == to_h
344
+ end
345
+ alias eql? ==
346
+
347
+ def hash
348
+ to_h.hash
349
+ end
350
+ end
351
+
352
+ # Result of creating a webhook. The `signing_secret` (`whsec_…`) is
353
+ # returned **once** — store it now; it is never shown again.
354
+ class CreatedWebhook
355
+ attr_reader :endpoint, :signing_secret
356
+
357
+ def initialize(endpoint:, signing_secret:)
358
+ @endpoint = endpoint
359
+ @signing_secret = signing_secret
360
+ freeze
361
+ end
362
+
363
+ def self.from_hash(hash)
364
+ new(
365
+ endpoint: Webhook.from_hash(hash['endpoint'] || {}),
366
+ signing_secret: hash['signing_secret']
367
+ )
368
+ end
369
+
370
+ def to_h
371
+ { endpoint: endpoint.to_h, signing_secret: signing_secret }
372
+ end
373
+
374
+ def ==(other)
375
+ other.is_a?(CreatedWebhook) && other.to_h == to_h
376
+ end
377
+ alias eql? ==
378
+
379
+ def hash
380
+ to_h.hash
381
+ end
382
+ end
383
+
384
+ # List of webhook endpoints plus every event type the server can deliver.
385
+ class ListWebhooksResult
386
+ attr_reader :endpoints, :event_types
387
+
388
+ def initialize(endpoints:, event_types:)
389
+ @endpoints = endpoints.freeze
390
+ @event_types = event_types.freeze
391
+ freeze
392
+ end
393
+
394
+ def self.from_hash(hash)
395
+ new(
396
+ endpoints: (hash['endpoints'] || []).map { |e| Webhook.from_hash(e) },
397
+ event_types: hash['event_types'] || []
398
+ )
399
+ end
400
+
401
+ def to_h
402
+ { endpoints: endpoints.map(&:to_h), event_types: event_types }
403
+ end
404
+
405
+ def ==(other)
406
+ other.is_a?(ListWebhooksResult) && other.to_h == to_h
407
+ end
408
+ alias eql? ==
409
+
410
+ def hash
411
+ to_h.hash
412
+ end
413
+ end
304
414
  end
305
415
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Rerout
4
4
  # Library version. Follows semantic versioning.
5
- VERSION = '0.2.0'
5
+ VERSION = '0.3.0'
6
6
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+
5
+ module Rerout
6
+ module Resources
7
+ # Webhook endpoint management namespace — create, list, delete endpoints
8
+ # for the project that owns the API key.
9
+ #
10
+ # This is distinct from {Rerout::Webhooks}, which verifies *inbound*
11
+ # delivery signatures. Reach it via `client.webhooks`.
12
+ class Webhooks
13
+ # @param client [Rerout::Client]
14
+ def initialize(client)
15
+ @client = client
16
+ end
17
+
18
+ # Create a webhook endpoint. The returned `signing_secret` (`whsec_…`) is
19
+ # shown once — persist it to verify future deliveries.
20
+ #
21
+ # @param input [Rerout::CreateWebhookInput, Hash] the request body.
22
+ # @return [Rerout::Models::CreatedWebhook]
23
+ def create(input)
24
+ body = coerce_input(input)
25
+ response = @client.request(method: :post, path: '/v1/projects/me/webhooks', body: body)
26
+ Models::CreatedWebhook.from_hash(response)
27
+ end
28
+
29
+ # List webhook endpoints and the event types the server can deliver.
30
+ #
31
+ # @return [Rerout::Models::ListWebhooksResult]
32
+ def list
33
+ response = @client.request(method: :get, path: '/v1/projects/me/webhooks')
34
+ Models::ListWebhooksResult.from_hash(response)
35
+ end
36
+
37
+ # Soft-delete an endpoint and abandon its pending deliveries. Idempotent.
38
+ #
39
+ # @param endpoint_id [String] the endpoint id (`wh_…`).
40
+ # @return [Hash] `{ "deleted" => true }`
41
+ def delete(endpoint_id)
42
+ @client.request(method: :delete, path: webhook_path(endpoint_id))
43
+ end
44
+
45
+ private
46
+
47
+ def webhook_path(endpoint_id)
48
+ raise ArgumentError, 'endpoint_id is required' if endpoint_id.nil? || endpoint_id.to_s.empty?
49
+
50
+ "/v1/projects/me/webhooks/#{ERB::Util.url_encode(endpoint_id.to_s)}"
51
+ end
52
+
53
+ def coerce_input(input)
54
+ case input
55
+ when CreateWebhookInput then input.to_h
56
+ when Hash then input
57
+ else
58
+ raise ArgumentError, 'input must be a Rerout::CreateWebhookInput or Hash'
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
data/lib/rerout.rb CHANGED
@@ -5,11 +5,13 @@ require_relative 'rerout/error'
5
5
  require_relative 'rerout/models'
6
6
  require_relative 'rerout/create_link_input'
7
7
  require_relative 'rerout/update_link_input'
8
+ require_relative 'rerout/create_webhook_input'
8
9
  require_relative 'rerout/qr_options'
9
10
  require_relative 'rerout/webhooks'
10
11
  require_relative 'rerout/links'
11
12
  require_relative 'rerout/project'
12
13
  require_relative 'rerout/qr'
14
+ require_relative 'rerout/webhooks_resource'
13
15
  require_relative 'rerout/client'
14
16
 
15
17
  # Official Ruby SDK for the Rerout branded-link API.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rerout
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Codecraft Solutions
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-02 00:00:00.000000000 Z
11
+ date: 2026-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -110,6 +110,7 @@ files:
110
110
  - lib/rerout.rb
111
111
  - lib/rerout/client.rb
112
112
  - lib/rerout/create_link_input.rb
113
+ - lib/rerout/create_webhook_input.rb
113
114
  - lib/rerout/error.rb
114
115
  - lib/rerout/links.rb
115
116
  - lib/rerout/models.rb
@@ -119,6 +120,7 @@ files:
119
120
  - lib/rerout/update_link_input.rb
120
121
  - lib/rerout/version.rb
121
122
  - lib/rerout/webhooks.rb
123
+ - lib/rerout/webhooks_resource.rb
122
124
  homepage: https://github.com/ModestNerds-Co/rerout-sdks
123
125
  licenses:
124
126
  - MIT