whop_sdk 1.0.11 → 1.0.13

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: 8187c35fd7e2a5826257f9cfe2910e80f3a75d15ddf36ae91ba98565be9a204e
4
- data.tar.gz: 47627c9b71a00846c63a860a64a593eda46723eaacee9238b4af842dac3b48d5
3
+ metadata.gz: 9fc15ef6d359b6cf4784e9b9b4a43a677cd03a56bebe3b559dbf7ee65a611f21
4
+ data.tar.gz: 771adb6fcaf1b7564ee7383eead94c7a909a24522ddb0709f0c7e0f8cdfd35e4
5
5
  SHA512:
6
- metadata.gz: 14db9c283348d1231147372234d2d8a1cf5bfe679e13cee3cd7770a122e350befd0bb2657a89f5794260e4812cae8b5904b5e7702de2c04b26c4e15b0df52492
7
- data.tar.gz: 36136fa35d4a5165336d3d185f1293d807da435f2790cbf831efe2eac2363b04fccf418224fdd5319deea2f44b74ddb93f8088dd8f66d016619cd6d463eb9d9c
6
+ metadata.gz: 62de0ee7e2ea242700d78b8e87bbc6f60257328bb7834cc9c8e66eee3338b1ac7f2510c2ea1ca6847e516fe154490ee97f6f23b0e9b2137ab71a872d3e8c324c
7
+ data.tar.gz: a587e338a0c270925b9de6b890cf4112a924c4906c5003b90d53f9b29ed172a11f18519db16261e186157e4d6f4493350f46acb3670a0a9777bcebab944ae13a
data/.fernignore CHANGED
@@ -20,25 +20,56 @@
20
20
  # one, so without this entry that history would disappear from the tree on the first
21
21
  # generated PR.
22
22
  #
23
- # lib/whop_sdk/helpers holds verify_user_token, the one hand-written helper in this SDK
24
- # the x-whop-user-token verifier. It depends only on the standard library and the jwt gem,
25
- # never on generated client code, so it survives the client being replaced. This entry only
26
- # keeps the file: two things it needs are in Fern-owned files and are restored from the
27
- # generator config in whop-monorepo's sdks/fern/generators.yml, not from here.
28
- #
29
- # - lib/whop_sdk.rb is regenerated, so the require_relative that loads the helper is lost.
30
- # `requirePaths: [helpers/verify_user_token]` re-emits it as a File.exist?-guarded
31
- # require_relative at the bottom of the generated lib/whop_sdk.rb.
32
- # - whop_sdk.gemspec is regenerated, so `add_dependency "jwt"` is lost, the same way
33
- # whopsdk-python's [user-tokens] extra does not survive. `extraDependencies: {jwt: ...}`
34
- # re-declares it.
35
- #
36
- # Those two travel together. The helper does `require "jwt"` at load, so re-emitting the
37
- # require without the dependency turns a silently missing verifier into a LoadError on
38
- # `require "whop_sdk"` — the gem stops loading for everyone, not just user-token callers.
39
- # ci.yml asserts all three every run so a regeneration that drops any of them fails there
23
+ # lib/whop_sdk/helpers holds the hand-written helpers in this SDK. Each depends only on the
24
+ # standard library and one third-party gem, never on generated client code, so each survives
25
+ # the client being replaced:
26
+ #
27
+ # - verify_user_token the x-whop-user-token verifier, on the jwt gem.
28
+ # - verify_webhook — the Standard Webhooks signature verifier, on the standardwebhooks gem.
29
+ # It restores the verification half of `client.webhooks.unwrap`, which the Stainless gem
30
+ # shipped through 0.0.41 and Fern cannot generate: Fern generates from OpenAPI paths and
31
+ # `unwrap` was never a path.
32
+ #
33
+ # This entry only keeps the files. Two things each helper needs are in Fern-owned files and
34
+ # are restored from the generator config in whop-monorepo's sdks/fern/generators.yml, not
35
+ # from here.
36
+ #
37
+ # - lib/whop_sdk.rb is regenerated, so the require_relative that loads a helper is lost.
38
+ # `requirePaths: [helpers/verify_user_token, helpers/verify_webhook]` re-emits them as
39
+ # File.exist?-guarded require_relatives at the bottom of the generated lib/whop_sdk.rb.
40
+ # - whop_sdk.gemspec is regenerated, so `add_dependency "jwt"` and
41
+ # `add_dependency "standardwebhooks"` are lost, the same way whopsdk-python's
42
+ # [user-tokens] extra does not survive. `extraDependencies: {jwt: ..., standardwebhooks: ...}`
43
+ # re-declares them.
44
+ #
45
+ # The two travel together, per helper. Each helper requires its gem at load, so re-emitting
46
+ # the require without the dependency turns a silently missing helper into a LoadError on
47
+ # `require "whop_sdk"` — the gem stops loading for everyone, not just that helper's callers.
48
+ # ci.yml asserts all of it every run so a regeneration that drops any of it fails there
40
49
  # instead of on a caller's machine.
50
+ #
51
+ # test/unit/helpers holds those helpers' tests. Fern owns test/, so without this entry a
52
+ # regeneration would prune them and the helpers would go untested rather than fail loudly.
53
+ #
54
+ # lib/whop_sdk/internal/iterators/cursor_page_iterator.rb is a patched copy of a generated
55
+ # file. The generated clients all pass cursor_field: :end_cursor, but the response models
56
+ # declare end_cursor on page_info rather than at the root and Internal::Types::Model has no
57
+ # method_missing — so the root lookup raised NoMethodError on the first page of every list
58
+ # in 1.0.11. The patch tries the root first and falls back to page_info, so it stays correct
59
+ # if the generator is fixed. Delete this entry and the file once fern-ruby-sdk emits the
60
+ # nested lookup itself.
61
+ #
62
+ # lib/whop_sdk/internal/types/utils.rb is a patched copy of a generated file. The Ruby
63
+ # generator maps every OpenAPI `number` to Integer — there is not one Float declaration in
64
+ # the gem, and `format: float` in the spec does not change it — so Utils.coerce's `value.to_i`
65
+ # silently truncated every decimal: initial_price 10.43 became 10, in both directions. The
66
+ # patch returns a Float unchanged when it has a fractional part, and still normalises whole
67
+ # floats to Integer so genuinely integral fields are unaffected. Delete this entry and the
68
+ # file once fern-ruby-sdk maps `number` to Float.
41
69
  .github/workflows/ci.yml
42
70
  .github/workflows/publish-main.yml
43
71
  CHANGELOG.md
44
72
  lib/whop_sdk/helpers
73
+ lib/whop_sdk/internal/iterators/cursor_page_iterator.rb
74
+ lib/whop_sdk/internal/types/utils.rb
75
+ test/unit/helpers
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "json"
5
+ require "standardwebhooks"
6
+
7
+ module WhopSDK
8
+ module Helpers
9
+ # Verifies the Standard Webhooks signature Whop sends on every webhook
10
+ # delivery, then parses the body.
11
+ #
12
+ # This is the verification half of the `client.webhooks.unwrap` the
13
+ # Stainless-generated gem shipped through 0.0.41. Fern generates from
14
+ # OpenAPI paths and `unwrap` was never a path, so the generated client has
15
+ # no equivalent. It is a standalone module rather than a method on the
16
+ # client so that nothing generated has to be patched: it depends only on
17
+ # the standardwebhooks gem and the standard library, never on generated
18
+ # client code, so it survives the client being replaced.
19
+ #
20
+ # What it does NOT do, and the Stainless version did: coerce the parsed
21
+ # body into one of 42 typed event models. Fern generates no webhook event
22
+ # models — `Whop_sdk::Types::WebhookEvent` is the enum of event *names* a
23
+ # webhook subscribes to, not a payload type — so there is nothing to coerce
24
+ # into. The parsed Hash is returned as-is.
25
+ module VerifyWebhook
26
+ MISSING_KEY_MESSAGE =
27
+ "Cannot verify a webhook without a key. Pass the endpoint's signing secret as `key:`."
28
+
29
+ # Verifies `payload` against the signature headers and returns the parsed body.
30
+ #
31
+ # @param payload [String] The raw, unmodified request body. Verifying a
32
+ # re-serialized body fails: the signature covers the exact bytes sent.
33
+ # @param headers [Hash] The request headers. Only `webhook-id`,
34
+ # `webhook-timestamp` and `webhook-signature` are read, and the lookup
35
+ # is case-insensitive.
36
+ # @param key [String] The endpoint's signing secret, exactly as Whop
37
+ # shows it — a `ws_`-prefixed string. Pass it verbatim; do not strip the
38
+ # prefix and do not pre-encode it.
39
+ # @return [Hash] The parsed body, with symbol keys.
40
+ # @raise [ArgumentError] when `key` is missing or empty.
41
+ # @raise [StandardWebhooks::WebhookVerificationError] when a signature
42
+ # header is missing, the timestamp is outside the tolerance window, or
43
+ # no signature matches.
44
+ def self.unwrap(payload, headers:, key:)
45
+ raise ArgumentError, MISSING_KEY_MESSAGE if key.nil? || key.to_s.empty?
46
+
47
+ ::StandardWebhooks::Webhook.new(hmac_key(key)).verify(payload, downcased_headers(headers))
48
+ JSON.parse(payload, symbolize_names: true)
49
+ end
50
+
51
+ # Whop's backend HMACs with the *literal bytes* of the secret it issued
52
+ # (`WebhooksManager::SignWebhook` passes `webhook.webhook_secret` straight
53
+ # to `OpenSSL::HMAC`). `StandardWebhooks::Webhook` instead base64-decodes
54
+ # whatever it is handed to derive its key, so handing it the secret raw
55
+ # derives the wrong key and every genuine delivery fails to verify.
56
+ # Encoding here cancels that decode out, leaving exactly the bytes the
57
+ # backend signed with.
58
+ #
59
+ # The whole secret is encoded, prefix included, because the backend never
60
+ # strips a prefix either. That also disarms the library's own `whsec_`
61
+ # stripping: base64 output cannot begin with `whsec_`, since `_` is not in
62
+ # the base64 alphabet.
63
+ #
64
+ # @api private
65
+ def self.hmac_key(key)
66
+ Base64.strict_encode64(key.to_s)
67
+ end
68
+
69
+ # StandardWebhooks::Webhook#verify looks its three headers up by exact
70
+ # lowercase key, but HTTP header names are case-insensitive and a plain
71
+ # Rack/Sinatra headers Hash arrives capitalized.
72
+ #
73
+ # @api private
74
+ def self.downcased_headers(headers)
75
+ raise ArgumentError, "headers must be a Hash-like object of header names to values" unless headers.respond_to?(:each_pair)
76
+
77
+ headers.each_pair.with_object({}) do |(name, value), downcased|
78
+ downcased[name.to_s.downcase] = value
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -35,7 +35,7 @@ module Whop_sdk
35
35
  @default_headers = {
36
36
  "X-Fern-Language": "Ruby",
37
37
  "X-Fern-SDK-Name": "whop_sdk",
38
- "X-Fern-SDK-Version": "1.0.11"
38
+ "X-Fern-SDK-Version": "1.0.13"
39
39
  }.merge(headers)
40
40
  @overridable_headers = overridable_headers.to_set { |name| name.to_s.downcase }
41
41
  end
@@ -55,9 +55,25 @@ module Whop_sdk
55
55
  else
56
56
  fetched_page = result
57
57
  end
58
- @cursor = fetched_page.send(@cursor_field)
58
+ @cursor = extract_cursor(fetched_page)
59
59
  fetched_page
60
60
  end
61
+
62
+ private
63
+
64
+ # The generated clients pass cursor_field: :end_cursor, but the response models
65
+ # declare end_cursor on page_info rather than at the root, and Internal::Types::Model
66
+ # has no method_missing — so the root lookup raises NoMethodError on the first page of
67
+ # every list. Remove this file from .fernignore once fern-ruby-sdk emits the nested
68
+ # lookup itself.
69
+ def extract_cursor(page)
70
+ return page.send(@cursor_field) if page.respond_to?(@cursor_field)
71
+
72
+ page_info = page.respond_to?(:page_info) ? page.page_info : nil
73
+ return page_info.send(@cursor_field) if page_info.respond_to?(@cursor_field)
74
+
75
+ nil
76
+ end
61
77
  end
62
78
  end
63
79
  end
@@ -58,6 +58,8 @@ module Whop_sdk
58
58
  end
59
59
  in ->(t) { t <= Integer }
60
60
  case value
61
+ when Float
62
+ return value == value.to_i ? value.to_i : value
61
63
  when Numeric, String, Time
62
64
  return value.to_i
63
65
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Whop_sdk
4
- VERSION = "1.0.11"
4
+ VERSION = "1.0.13"
5
5
  end
data/lib/whop_sdk.rb CHANGED
@@ -3002,7 +3002,7 @@ require_relative "whop_sdk/environment"
3002
3002
 
3003
3003
  # Load user-defined files if present (e.g., for Sentry integration)
3004
3004
  # Files are loaded from lib/whop_sdk/ if they exist
3005
- ["whop_sdk/helpers/verify_user_token"].each do |relative_path|
3005
+ ["whop_sdk/helpers/verify_user_token", "whop_sdk/helpers/verify_webhook"].each do |relative_path|
3006
3006
  absolute_path = File.join(__dir__, "#{relative_path}.rb")
3007
3007
  require_relative relative_path if File.exist?(absolute_path)
3008
3008
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: whop_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.11
4
+ version: 1.0.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Whop_sdk
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-08-22 00:00:00.000000000 Z
11
+ date: 2026-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jwt
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: standardwebhooks
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
27
41
  description: The Whop_sdk Ruby library provides convenient access to the Whop_sdk
28
42
  API from Ruby.
29
43
  email:
@@ -886,6 +900,7 @@ files:
886
900
  - lib/whop_sdk/forums/types/update_forums_request.rb
887
901
  - lib/whop_sdk/forums/types/update_forums_request_banner_image.rb
888
902
  - lib/whop_sdk/helpers/verify_user_token.rb
903
+ - lib/whop_sdk/helpers/verify_webhook.rb
889
904
  - lib/whop_sdk/identity_profiles/client.rb
890
905
  - lib/whop_sdk/identity_profiles/types/list_identity_profile_request.rb
891
906
  - lib/whop_sdk/identity_profiles/types/list_identity_profile_response.rb