kit-rb 0.2.0 → 0.3.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/.githooks/pre-commit +21 -0
- data/.githooks/pre-push +6 -0
- data/CHANGELOG.md +84 -0
- data/README.md +66 -16
- data/docs/DESIGN.md +38 -14
- data/docs/TASKS.md +47 -0
- data/lib/kit/auth/api_key.rb +7 -0
- data/lib/kit/auth/credential.rb +22 -0
- data/lib/kit/auth/oauth.rb +7 -8
- data/lib/kit/configuration.rb +3 -2
- data/lib/kit/connection.rb +53 -20
- data/lib/kit/errors.rb +52 -15
- data/lib/kit/oauth/token.rb +9 -0
- data/lib/kit/objects/account.rb +44 -3
- data/lib/kit/objects/bulk_result.rb +39 -0
- data/lib/kit/objects/custom_field.rb +4 -2
- data/lib/kit/objects/post.rb +4 -2
- data/lib/kit/objects/sequence_email.rb +5 -2
- data/lib/kit/objects/subscriber.rb +16 -9
- data/lib/kit/objects/tag.rb +5 -3
- data/lib/kit/objects/webhook_endpoint.rb +7 -2
- data/lib/kit/pagination.rb +47 -3
- data/lib/kit/resources/account.rb +4 -4
- data/lib/kit/resources/base.rb +46 -4
- data/lib/kit/resources/broadcasts.rb +32 -13
- data/lib/kit/resources/bulk.rb +18 -20
- data/lib/kit/resources/custom_fields.rb +2 -2
- data/lib/kit/resources/forms.rb +3 -3
- data/lib/kit/resources/posts.rb +1 -1
- data/lib/kit/resources/purchases.rb +10 -6
- data/lib/kit/resources/sequences.rb +49 -25
- data/lib/kit/resources/snippets.rb +13 -8
- data/lib/kit/resources/subscribers.rb +23 -9
- data/lib/kit/resources/tags.rb +6 -6
- data/lib/kit/resources/webhook_endpoints.rb +12 -4
- data/lib/kit/resources/webhooks.rb +1 -1
- data/lib/kit/version.rb +1 -1
- data/lib/kit/webhooks/delivery.rb +61 -0
- data/lib/kit/webhooks/events.rb +113 -0
- data/lib/kit/webhooks/signature.rb +100 -0
- data/lib/kit-rb.rb +5 -0
- data/sig/kit-rb.rbs +172 -30
- metadata +19 -3
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kit
|
|
4
|
+
module Webhooks
|
|
5
|
+
# Event types a webhook endpoint (/v4/webhook_endpoints) can subscribe to,
|
|
6
|
+
# as listed at developers.kit.com/webhooks/event-types (verified
|
|
7
|
+
# 2026-09-06). Use these instead of hand-typed strings:
|
|
8
|
+
#
|
|
9
|
+
# client.webhook_endpoints.create(url: url, events: [Events::SUBSCRIBER_CREATED, Events::TAG_CREATED])
|
|
10
|
+
#
|
|
11
|
+
# DATA_KEYS names the keys each event's `data` carries. PLANNED events are
|
|
12
|
+
# documented but not yet delivered.
|
|
13
|
+
module Events
|
|
14
|
+
SUBSCRIBER_CREATED = "subscriber.created"
|
|
15
|
+
SUBSCRIBER_ACTIVATED = "subscriber.activated"
|
|
16
|
+
SUBSCRIBER_UNSUBSCRIBED = "subscriber.unsubscribed"
|
|
17
|
+
SUBSCRIBER_BOUNCED = "subscriber.bounced"
|
|
18
|
+
SUBSCRIBER_COMPLAINED = "subscriber.complained"
|
|
19
|
+
SUBSCRIBER_SUBSCRIBED_TO_FORM = "subscriber.subscribed_to_form"
|
|
20
|
+
SUBSCRIBER_ADDED_TO_SEQUENCE = "subscriber.added_to_sequence"
|
|
21
|
+
SUBSCRIBER_SEQUENCE_COMPLETED = "subscriber.sequence_completed"
|
|
22
|
+
SUBSCRIBER_TAG_ADDED = "subscriber.tag_added"
|
|
23
|
+
SUBSCRIBER_TAG_REMOVED = "subscriber.tag_removed"
|
|
24
|
+
SUBSCRIBER_CUSTOM_FIELD_VALUE_UPDATED = "subscriber.custom_field_value_updated"
|
|
25
|
+
SUBSCRIBER_PRODUCT_PURCHASED = "subscriber.product_purchased" # planned
|
|
26
|
+
SUBSCRIBER_LINK_CLICKED = "subscriber.link_clicked" # planned
|
|
27
|
+
SUBSCRIBER_EMAIL_OPENED = "subscriber.email_opened" # planned
|
|
28
|
+
TAG_CREATED = "tag.created"
|
|
29
|
+
TAG_DELETED = "tag.deleted"
|
|
30
|
+
CUSTOM_FIELD_CREATED = "custom_field.created"
|
|
31
|
+
CUSTOM_FIELD_DELETED = "custom_field.deleted"
|
|
32
|
+
SEQUENCE_CREATED = "sequence.created"
|
|
33
|
+
SEQUENCE_DELETED = "sequence.deleted"
|
|
34
|
+
SEQUENCE_PUBLISHED = "sequence.published"
|
|
35
|
+
SEQUENCE_DISABLED = "sequence.disabled"
|
|
36
|
+
BROADCAST_CREATED = "broadcast.created"
|
|
37
|
+
BROADCAST_SENT = "broadcast.sent"
|
|
38
|
+
BROADCAST_DELETED = "broadcast.deleted"
|
|
39
|
+
POST_PUBLISHED = "post.published"
|
|
40
|
+
LANDING_PAGE_CREATED = "landing_page.created" # planned
|
|
41
|
+
LANDING_PAGE_DELETED = "landing_page.deleted" # planned
|
|
42
|
+
|
|
43
|
+
DATA_KEYS = {
|
|
44
|
+
SUBSCRIBER_CREATED => %w[subscriber], SUBSCRIBER_ACTIVATED => %w[subscriber],
|
|
45
|
+
SUBSCRIBER_UNSUBSCRIBED => %w[subscriber], SUBSCRIBER_BOUNCED => %w[subscriber],
|
|
46
|
+
SUBSCRIBER_COMPLAINED => %w[subscriber], SUBSCRIBER_SUBSCRIBED_TO_FORM => %w[subscriber form],
|
|
47
|
+
SUBSCRIBER_ADDED_TO_SEQUENCE => %w[subscriber sequence],
|
|
48
|
+
SUBSCRIBER_SEQUENCE_COMPLETED => %w[subscriber sequence],
|
|
49
|
+
SUBSCRIBER_TAG_ADDED => %w[subscriber tag], SUBSCRIBER_TAG_REMOVED => %w[subscriber tag],
|
|
50
|
+
SUBSCRIBER_CUSTOM_FIELD_VALUE_UPDATED => %w[subscriber custom_field],
|
|
51
|
+
SUBSCRIBER_PRODUCT_PURCHASED => %w[subscriber], SUBSCRIBER_LINK_CLICKED => %w[subscriber],
|
|
52
|
+
SUBSCRIBER_EMAIL_OPENED => %w[subscriber],
|
|
53
|
+
TAG_CREATED => %w[tag], TAG_DELETED => %w[tag],
|
|
54
|
+
CUSTOM_FIELD_CREATED => %w[custom_field], CUSTOM_FIELD_DELETED => %w[custom_field],
|
|
55
|
+
SEQUENCE_CREATED => %w[sequence], SEQUENCE_DELETED => %w[sequence],
|
|
56
|
+
SEQUENCE_PUBLISHED => %w[sequence], SEQUENCE_DISABLED => %w[sequence],
|
|
57
|
+
BROADCAST_CREATED => %w[broadcast], BROADCAST_SENT => %w[broadcast], BROADCAST_DELETED => %w[broadcast],
|
|
58
|
+
POST_PUBLISHED => %w[post],
|
|
59
|
+
LANDING_PAGE_CREATED => [], LANDING_PAGE_DELETED => []
|
|
60
|
+
}.freeze
|
|
61
|
+
|
|
62
|
+
PLANNED = [
|
|
63
|
+
SUBSCRIBER_PRODUCT_PURCHASED, SUBSCRIBER_LINK_CLICKED, SUBSCRIBER_EMAIL_OPENED,
|
|
64
|
+
LANDING_PAGE_CREATED, LANDING_PAGE_DELETED
|
|
65
|
+
].freeze
|
|
66
|
+
|
|
67
|
+
ALL = DATA_KEYS.keys.freeze
|
|
68
|
+
AVAILABLE = (ALL - PLANNED).freeze
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Event names for the previous-generation /v4/webhooks (one event per
|
|
72
|
+
# webhook, unsigned). Kept for existing integrations; new ones should use
|
|
73
|
+
# webhook endpoints. REQUIRED_PARAM names the extra key the `event` hash
|
|
74
|
+
# must carry for the events that are scoped to a resource:
|
|
75
|
+
#
|
|
76
|
+
# client.webhooks.create(target_url: url,
|
|
77
|
+
# event: { name: LegacyEvents::TAG_ADD, tag_id: 12 })
|
|
78
|
+
module LegacyEvents
|
|
79
|
+
SUBSCRIBER_ACTIVATE = "subscriber.subscriber_activate"
|
|
80
|
+
SUBSCRIBER_UNSUBSCRIBE = "subscriber.subscriber_unsubscribe"
|
|
81
|
+
SUBSCRIBER_BOUNCE = "subscriber.subscriber_bounce"
|
|
82
|
+
SUBSCRIBER_COMPLAIN = "subscriber.subscriber_complain"
|
|
83
|
+
FORM_SUBSCRIBE = "subscriber.form_subscribe"
|
|
84
|
+
COURSE_SUBSCRIBE = "subscriber.course_subscribe"
|
|
85
|
+
COURSE_COMPLETE = "subscriber.course_complete"
|
|
86
|
+
LINK_CLICK = "subscriber.link_click"
|
|
87
|
+
PRODUCT_PURCHASE = "subscriber.product_purchase"
|
|
88
|
+
TAG_ADD = "subscriber.tag_add"
|
|
89
|
+
TAG_REMOVE = "subscriber.tag_remove"
|
|
90
|
+
PURCHASE_CREATE = "purchase.purchase_create"
|
|
91
|
+
FIELD_CREATED = "custom_field.field_created"
|
|
92
|
+
FIELD_DELETED = "custom_field.field_deleted"
|
|
93
|
+
FIELD_VALUE_UPDATED = "custom_field.field_value_updated"
|
|
94
|
+
|
|
95
|
+
REQUIRED_PARAM = {
|
|
96
|
+
FORM_SUBSCRIBE => :form_id,
|
|
97
|
+
COURSE_SUBSCRIBE => :sequence_id,
|
|
98
|
+
COURSE_COMPLETE => :sequence_id,
|
|
99
|
+
LINK_CLICK => :initiator_value,
|
|
100
|
+
PRODUCT_PURCHASE => :product_id,
|
|
101
|
+
TAG_ADD => :tag_id,
|
|
102
|
+
TAG_REMOVE => :tag_id,
|
|
103
|
+
FIELD_VALUE_UPDATED => :custom_field_id
|
|
104
|
+
}.freeze
|
|
105
|
+
|
|
106
|
+
ALL = [
|
|
107
|
+
SUBSCRIBER_ACTIVATE, SUBSCRIBER_UNSUBSCRIBE, SUBSCRIBER_BOUNCE, SUBSCRIBER_COMPLAIN,
|
|
108
|
+
FORM_SUBSCRIBE, COURSE_SUBSCRIBE, COURSE_COMPLETE, LINK_CLICK, PRODUCT_PURCHASE,
|
|
109
|
+
TAG_ADD, TAG_REMOVE, PURCHASE_CREATE, FIELD_CREATED, FIELD_DELETED, FIELD_VALUE_UPDATED
|
|
110
|
+
].freeze
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "openssl"
|
|
4
|
+
|
|
5
|
+
module Kit
|
|
6
|
+
module Webhooks
|
|
7
|
+
# Raised when a delivery's X-Kit-Signature is missing, malformed, stale, or
|
|
8
|
+
# does not match the body under any supplied secret.
|
|
9
|
+
class SignatureError < Error; end
|
|
10
|
+
|
|
11
|
+
# Verifies the `X-Kit-Signature` header Kit sends with every webhook-endpoint
|
|
12
|
+
# delivery (verified against developers.kit.com/webhooks/verifying-signatures,
|
|
13
|
+
# 2026-09-06):
|
|
14
|
+
#
|
|
15
|
+
# X-Kit-Signature: t=1753797130,v1=<hex>[,v1=<hex>]
|
|
16
|
+
#
|
|
17
|
+
# Each v1 is hex(HMAC-SHA256(secret, "#{t}.#{raw_body}")). During a secret
|
|
18
|
+
# rotation the header carries two v1 entries (new and previous secret), so
|
|
19
|
+
# a delivery is valid when any v1 matches any of the secrets you hold.
|
|
20
|
+
#
|
|
21
|
+
# Kit::Webhooks::Signature.verify!(request.raw_post, request.headers["X-Kit-Signature"],
|
|
22
|
+
# secret: ENV["KIT_WEBHOOK_SECRET"])
|
|
23
|
+
module Signature
|
|
24
|
+
HEADER = "X-Kit-Signature"
|
|
25
|
+
SCHEME = "v1"
|
|
26
|
+
DEFAULT_TOLERANCE = 300 # seconds; Kit's recommended replay window
|
|
27
|
+
|
|
28
|
+
Parsed = Data.define(:timestamp, :signatures)
|
|
29
|
+
|
|
30
|
+
# hex(HMAC-SHA256(secret, "t.payload")) — what Kit puts in v1.
|
|
31
|
+
def self.compute(secret, timestamp, payload)
|
|
32
|
+
OpenSSL::HMAC.hexdigest("SHA256", secret, "#{timestamp}.#{payload}")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Splits the header into its timestamp and v1 signatures. Pairs are kept
|
|
36
|
+
# as a list, not a Hash: a rotation sends two v1 entries.
|
|
37
|
+
def self.parse(header)
|
|
38
|
+
raise SignatureError, "missing #{HEADER} header" if header.nil? || header.strip.empty?
|
|
39
|
+
|
|
40
|
+
pairs = header.split(",").map { |pair| pair.strip.split("=", 2) }
|
|
41
|
+
signatures = pairs.filter_map { |key, value| value if key == SCHEME }
|
|
42
|
+
raise SignatureError, "malformed #{HEADER} header: #{header.inspect}" if signatures.empty?
|
|
43
|
+
|
|
44
|
+
Parsed.new(timestamp: parse_timestamp(pairs, header), signatures: signatures)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.parse_timestamp(pairs, header)
|
|
48
|
+
value = pairs.find { |key, _| key == "t" }&.last
|
|
49
|
+
raise SignatureError, "malformed #{HEADER} header: #{header.inspect}" if value.nil?
|
|
50
|
+
raise SignatureError, "malformed timestamp in #{HEADER}: #{value.inspect}" unless value.match?(/\A\d+\z/)
|
|
51
|
+
|
|
52
|
+
value.to_i
|
|
53
|
+
end
|
|
54
|
+
private_class_method :parse_timestamp
|
|
55
|
+
|
|
56
|
+
# True when the payload was signed by one of `secret` (a String, or an
|
|
57
|
+
# Array of Strings while you hold both sides of a rotation) within
|
|
58
|
+
# `tolerance` seconds of `now`. Constant-time comparison.
|
|
59
|
+
def self.verify?(payload, header, secret:, tolerance: DEFAULT_TOLERANCE, now: Time.now.to_i)
|
|
60
|
+
verify!(payload, header, secret: secret, tolerance: tolerance, now: now)
|
|
61
|
+
true
|
|
62
|
+
rescue SignatureError
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# As #verify?, but raises SignatureError describing why the delivery was
|
|
67
|
+
# rejected. `payload` must be the raw request body, byte for byte.
|
|
68
|
+
def self.verify!(payload, header, secret:, tolerance: DEFAULT_TOLERANCE, now: Time.now.to_i)
|
|
69
|
+
secrets = secrets_from(secret)
|
|
70
|
+
parsed = parse(header)
|
|
71
|
+
check_freshness(parsed.timestamp, tolerance, now)
|
|
72
|
+
return true if secrets.any? { |value| matches?(value, parsed, payload) }
|
|
73
|
+
|
|
74
|
+
raise SignatureError, "no #{SCHEME} signature matched the payload"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.secrets_from(secret)
|
|
78
|
+
secrets = Array(secret).reject { |value| value.nil? || value.empty? }
|
|
79
|
+
raise ArgumentError, "at least one webhook secret is required" if secrets.empty?
|
|
80
|
+
|
|
81
|
+
secrets
|
|
82
|
+
end
|
|
83
|
+
private_class_method :secrets_from
|
|
84
|
+
|
|
85
|
+
def self.check_freshness(timestamp, tolerance, now)
|
|
86
|
+
return unless tolerance
|
|
87
|
+
|
|
88
|
+
age = (now - timestamp).abs
|
|
89
|
+
raise SignatureError, "timestamp outside tolerance (#{age}s > #{tolerance}s)" if age > tolerance
|
|
90
|
+
end
|
|
91
|
+
private_class_method :check_freshness
|
|
92
|
+
|
|
93
|
+
def self.matches?(secret, parsed, payload)
|
|
94
|
+
expected = compute(secret, parsed.timestamp, payload)
|
|
95
|
+
parsed.signatures.any? { |given| OpenSSL.secure_compare(expected, given) }
|
|
96
|
+
end
|
|
97
|
+
private_class_method :matches?
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
data/lib/kit-rb.rb
CHANGED
|
@@ -15,6 +15,7 @@ end
|
|
|
15
15
|
require "kit/version"
|
|
16
16
|
require "kit/errors"
|
|
17
17
|
require "kit/configuration"
|
|
18
|
+
require "kit/auth/credential"
|
|
18
19
|
require "kit/auth/api_key"
|
|
19
20
|
require "kit/auth/oauth"
|
|
20
21
|
require "kit/connection"
|
|
@@ -43,6 +44,10 @@ require "kit/objects/segment"
|
|
|
43
44
|
require "kit/objects/post"
|
|
44
45
|
require "kit/objects/snippet"
|
|
45
46
|
require "kit/objects/purchase"
|
|
47
|
+
require "kit/objects/bulk_result"
|
|
48
|
+
require "kit/webhooks/signature"
|
|
49
|
+
require "kit/webhooks/delivery"
|
|
50
|
+
require "kit/webhooks/events"
|
|
46
51
|
require "kit/resources/base"
|
|
47
52
|
require "kit/resources/account"
|
|
48
53
|
require "kit/resources/subscribers"
|
data/sig/kit-rb.rbs
CHANGED
|
@@ -1,22 +1,35 @@
|
|
|
1
|
-
# RBS type signatures for the public surface of kit-rb
|
|
2
|
-
#
|
|
1
|
+
# RBS type signatures for the public surface of kit-rb, checked by Steep
|
|
2
|
+
# (`bundle exec rake steep`). Keep in step with lib/.
|
|
3
3
|
|
|
4
4
|
module Kit
|
|
5
5
|
VERSION: String
|
|
6
6
|
DEFAULT_BASE_URL: String
|
|
7
7
|
|
|
8
8
|
class Error < StandardError
|
|
9
|
+
STATUS_CLASSES: Hash[Integer, singleton(APIError)]
|
|
9
10
|
def self.class_for: (Integer status) -> singleton(APIError)
|
|
10
11
|
end
|
|
11
12
|
class ConfigurationError < Error
|
|
12
13
|
end
|
|
14
|
+
class TransportError < Error
|
|
15
|
+
end
|
|
16
|
+
class TimeoutError < TransportError
|
|
17
|
+
end
|
|
18
|
+
class ConnectionError < TransportError
|
|
19
|
+
end
|
|
20
|
+
class UnexpectedResponseError < Error
|
|
21
|
+
attr_reader body: untyped
|
|
22
|
+
def initialize: (String message, ?body: untyped) -> void
|
|
23
|
+
end
|
|
13
24
|
|
|
14
25
|
class APIError < Error
|
|
15
26
|
attr_reader status: Integer
|
|
16
27
|
attr_reader body: untyped
|
|
17
28
|
attr_reader errors: Array[untyped]
|
|
18
29
|
attr_reader response: untyped
|
|
19
|
-
|
|
30
|
+
attr_reader method: Symbol?
|
|
31
|
+
attr_reader path: String?
|
|
32
|
+
def initialize: (?String? message, status: Integer, ?body: untyped, ?response: untyped, ?method: Symbol?, ?path: String?) -> void
|
|
20
33
|
end
|
|
21
34
|
class AuthenticationError < APIError
|
|
22
35
|
end
|
|
@@ -24,11 +37,15 @@ module Kit
|
|
|
24
37
|
end
|
|
25
38
|
class NotFoundError < APIError
|
|
26
39
|
end
|
|
40
|
+
class ConflictError < APIError
|
|
41
|
+
end
|
|
42
|
+
class PayloadTooLargeError < APIError
|
|
43
|
+
end
|
|
27
44
|
class UnprocessableEntityError < APIError
|
|
28
45
|
end
|
|
29
46
|
class RateLimitError < APIError
|
|
30
47
|
attr_reader retry_after: Integer?
|
|
31
|
-
def initialize: (?String? message, status: Integer, ?body: untyped, ?response: untyped, ?retry_after: Integer?) -> void
|
|
48
|
+
def initialize: (?String? message, status: Integer, ?body: untyped, ?response: untyped, ?method: Symbol?, ?path: String?, ?retry_after: Integer?) -> void
|
|
32
49
|
end
|
|
33
50
|
class ServerError < APIError
|
|
34
51
|
end
|
|
@@ -57,6 +74,8 @@ module Kit
|
|
|
57
74
|
attr_reader scope: String?
|
|
58
75
|
attr_reader created_at: Integer?
|
|
59
76
|
def self.from: (Hash[String, untyped] hash) -> Token
|
|
77
|
+
def inspect: () -> String
|
|
78
|
+
def to_s: () -> String
|
|
60
79
|
def expires_at: () -> Integer?
|
|
61
80
|
def expired?: (?now: Integer, ?leeway: Integer) -> bool
|
|
62
81
|
end
|
|
@@ -72,18 +91,70 @@ module Kit
|
|
|
72
91
|
end
|
|
73
92
|
end
|
|
74
93
|
|
|
94
|
+
module Webhooks
|
|
95
|
+
class SignatureError < Error
|
|
96
|
+
end
|
|
97
|
+
module Signature
|
|
98
|
+
HEADER: String
|
|
99
|
+
SCHEME: String
|
|
100
|
+
DEFAULT_TOLERANCE: Integer
|
|
101
|
+
class Parsed
|
|
102
|
+
attr_reader timestamp: Integer
|
|
103
|
+
attr_reader signatures: Array[String]
|
|
104
|
+
end
|
|
105
|
+
def self.compute: (String secret, Integer | String timestamp, String payload) -> String
|
|
106
|
+
def self.parse: (String? header) -> Parsed
|
|
107
|
+
def self.verify?: (String payload, String? header, secret: String | Array[String], ?tolerance: Integer?, ?now: Integer) -> bool
|
|
108
|
+
def self.verify!: (String payload, String? header, secret: String | Array[String], ?tolerance: Integer?, ?now: Integer) -> bool
|
|
109
|
+
end
|
|
110
|
+
class Event
|
|
111
|
+
attr_reader id: String?
|
|
112
|
+
attr_reader type: String?
|
|
113
|
+
attr_reader created: String?
|
|
114
|
+
attr_reader data: Hash[String, untyped]
|
|
115
|
+
def self.from: (Hash[String, untyped] hash) -> Event
|
|
116
|
+
end
|
|
117
|
+
module Events
|
|
118
|
+
DATA_KEYS: Hash[String, Array[String]]
|
|
119
|
+
PLANNED: Array[String]
|
|
120
|
+
ALL: Array[String]
|
|
121
|
+
AVAILABLE: Array[String]
|
|
122
|
+
end
|
|
123
|
+
module LegacyEvents
|
|
124
|
+
REQUIRED_PARAM: Hash[String, Symbol]
|
|
125
|
+
ALL: Array[String]
|
|
126
|
+
end
|
|
127
|
+
DELIVERY_HEADER: String
|
|
128
|
+
class Delivery
|
|
129
|
+
attr_reader delivery_id: Integer?
|
|
130
|
+
attr_reader events: Array[Event]
|
|
131
|
+
def self.from: (Hash[String, untyped] hash) -> Delivery
|
|
132
|
+
def self.parse: (String payload) -> Delivery
|
|
133
|
+
def self.from_request: (String payload, String? signature_header, secret: String | Array[String], ?tolerance: Integer?, ?now: Integer) -> Delivery
|
|
134
|
+
def type: () -> String?
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
75
138
|
module Auth
|
|
139
|
+
module Credential
|
|
140
|
+
MASK: String
|
|
141
|
+
VISIBLE: Integer
|
|
142
|
+
def self.mask: (untyped secret) -> String
|
|
143
|
+
end
|
|
144
|
+
|
|
76
145
|
class ApiKey
|
|
77
146
|
HEADER: String
|
|
78
147
|
def initialize: (String key) -> void
|
|
79
148
|
def headers: () -> Hash[String, String]
|
|
149
|
+
def inspect: () -> String
|
|
150
|
+
def to_s: () -> String
|
|
80
151
|
end
|
|
81
152
|
|
|
82
153
|
class OAuth
|
|
83
|
-
AUTHORIZE_URL: String
|
|
84
|
-
TOKEN_URL: String
|
|
85
154
|
def initialize: (String access_token) -> void
|
|
86
155
|
def headers: () -> Hash[String, String]
|
|
156
|
+
def inspect: () -> String
|
|
157
|
+
def to_s: () -> String
|
|
87
158
|
end
|
|
88
159
|
end
|
|
89
160
|
|
|
@@ -92,17 +163,21 @@ module Kit
|
|
|
92
163
|
attr_reader base_url: String
|
|
93
164
|
attr_reader open_timeout: Integer
|
|
94
165
|
attr_reader read_timeout: Integer
|
|
166
|
+
attr_reader write_timeout: Integer
|
|
95
167
|
attr_reader max_retries: Integer
|
|
96
168
|
attr_reader retry_backoff: Float
|
|
97
169
|
attr_reader max_backoff: Numeric
|
|
98
|
-
def initialize: (?api_key: String?, ?access_token: String?, ?base_url: String, ?open_timeout: Integer, ?read_timeout: Integer, ?max_retries: Integer, ?retry_backoff: Float, ?max_backoff: Numeric) -> void
|
|
170
|
+
def initialize: (?api_key: String?, ?access_token: String?, ?base_url: String, ?open_timeout: Integer, ?read_timeout: Integer, ?write_timeout: Integer, ?max_retries: Integer, ?retry_backoff: Float, ?max_backoff: Numeric) -> void
|
|
99
171
|
end
|
|
100
172
|
|
|
101
173
|
class Connection
|
|
102
174
|
JSON_TYPE: String
|
|
103
|
-
RETRYABLE: Array[singleton(
|
|
175
|
+
RETRYABLE: Array[singleton(Error)]
|
|
176
|
+
IDEMPOTENT: Array[Symbol]
|
|
104
177
|
def initialize: (Configuration config) -> void
|
|
105
178
|
def request: (Symbol method, String path, ?params: Hash[untyped, untyped], ?body: Hash[untyped, untyped]?) -> untyped
|
|
179
|
+
def request_with_status: (Symbol method, String path, ?params: Hash[untyped, untyped], ?body: Hash[untyped, untyped]?) -> [Integer, untyped]
|
|
180
|
+
def inspect: () -> String
|
|
106
181
|
end
|
|
107
182
|
|
|
108
183
|
class Pagination
|
|
@@ -111,11 +186,20 @@ module Kit
|
|
|
111
186
|
attr_reader start_cursor: String?
|
|
112
187
|
attr_reader end_cursor: String?
|
|
113
188
|
attr_reader per_page: Integer?
|
|
189
|
+
attr_reader total_count: Integer?
|
|
114
190
|
def self.from: (Hash[String, untyped] hash) -> Pagination
|
|
115
191
|
end
|
|
116
192
|
|
|
117
193
|
class Collection[T]
|
|
118
194
|
include Enumerable[T]
|
|
195
|
+
FIRST_PAGE_ONLY: Array[Symbol]
|
|
196
|
+
def self.next_page_params: (Hash[Symbol, untyped] params, String? after) -> Hash[Symbol, untyped]
|
|
197
|
+
def total_count: () -> Integer?
|
|
198
|
+
def size: () -> Integer
|
|
199
|
+
def length: () -> Integer
|
|
200
|
+
def empty?: () -> bool
|
|
201
|
+
def []: (Integer index) -> T?
|
|
202
|
+
def inspect: () -> String
|
|
119
203
|
attr_reader data: Array[T]
|
|
120
204
|
attr_reader pagination: Pagination
|
|
121
205
|
def initialize: (data: Array[T], pagination: Pagination) { (String end_cursor) -> Collection[T] } -> void
|
|
@@ -131,12 +215,40 @@ module Kit
|
|
|
131
215
|
attr_reader id: Integer?
|
|
132
216
|
def self.from: (Hash[String, untyped] hash) -> User
|
|
133
217
|
end
|
|
218
|
+
class Timezone
|
|
219
|
+
attr_reader name: String?
|
|
220
|
+
attr_reader friendly_name: String?
|
|
221
|
+
attr_reader utc_offset: String?
|
|
222
|
+
def self.from: (Hash[String, untyped] hash) -> Timezone
|
|
223
|
+
end
|
|
224
|
+
class Plan
|
|
225
|
+
attr_reader plan_type: String?
|
|
226
|
+
attr_reader interval: String?
|
|
227
|
+
attr_reader subscriber_limit: Integer?
|
|
228
|
+
attr_reader on_trial: bool?
|
|
229
|
+
attr_reader trial_lapse_date: String?
|
|
230
|
+
attr_reader renews_at: String?
|
|
231
|
+
attr_reader cancels_at: String?
|
|
232
|
+
def self.from: (Hash[String, untyped] hash) -> Plan
|
|
233
|
+
end
|
|
234
|
+
class SendingAddress
|
|
235
|
+
attr_reader email_address: String?
|
|
236
|
+
attr_reader from_name: String?
|
|
237
|
+
attr_reader status: String?
|
|
238
|
+
attr_reader is_default: bool?
|
|
239
|
+
attr_reader is_verified: bool?
|
|
240
|
+
attr_reader is_dmarc_configured: bool?
|
|
241
|
+
def self.from: (Hash[String, untyped] hash) -> SendingAddress
|
|
242
|
+
end
|
|
134
243
|
class Account
|
|
135
244
|
attr_reader id: Integer?
|
|
136
245
|
attr_reader name: String?
|
|
137
246
|
attr_reader plan_type: String?
|
|
138
247
|
attr_reader primary_email_address: String?
|
|
139
248
|
attr_reader created_at: String?
|
|
249
|
+
attr_reader timezone: Timezone?
|
|
250
|
+
attr_reader plan: Plan?
|
|
251
|
+
attr_reader sending_addresses: Array[SendingAddress]
|
|
140
252
|
def self.from: (Hash[String, untyped] hash) -> Account
|
|
141
253
|
end
|
|
142
254
|
class AccountInfo
|
|
@@ -157,6 +269,7 @@ module Kit
|
|
|
157
269
|
attr_reader name: String?
|
|
158
270
|
attr_reader created_at: String?
|
|
159
271
|
attr_reader subscriber_count: Integer?
|
|
272
|
+
attr_reader tagged_at: String?
|
|
160
273
|
def self.from: (Hash[String, untyped] hash) -> Tag
|
|
161
274
|
end
|
|
162
275
|
class CustomField
|
|
@@ -164,6 +277,7 @@ module Kit
|
|
|
164
277
|
attr_reader name: String?
|
|
165
278
|
attr_reader key: String?
|
|
166
279
|
attr_reader label: String?
|
|
280
|
+
attr_reader created_at: String?
|
|
167
281
|
def self.from: (Hash[String, untyped] hash) -> CustomField
|
|
168
282
|
end
|
|
169
283
|
class Form
|
|
@@ -236,6 +350,7 @@ module Kit
|
|
|
236
350
|
attr_reader created_by_app: untyped
|
|
237
351
|
attr_reader created_at: String?
|
|
238
352
|
attr_reader previous_secret_expires_at: String?
|
|
353
|
+
attr_reader secret: String?
|
|
239
354
|
def self.from: (Hash[String, untyped] hash) -> WebhookEndpoint
|
|
240
355
|
end
|
|
241
356
|
class EmailTemplate
|
|
@@ -266,6 +381,7 @@ module Kit
|
|
|
266
381
|
attr_reader thumbnail_url: String?
|
|
267
382
|
attr_reader is_paid: bool?
|
|
268
383
|
attr_reader public_url: String?
|
|
384
|
+
attr_reader content: String?
|
|
269
385
|
def self.from: (Hash[String, untyped] hash) -> Post
|
|
270
386
|
end
|
|
271
387
|
class Snippet
|
|
@@ -280,6 +396,19 @@ module Kit
|
|
|
280
396
|
attr_reader document: untyped
|
|
281
397
|
def self.from: (Hash[String, untyped] hash) -> Snippet
|
|
282
398
|
end
|
|
399
|
+
class BulkFailure
|
|
400
|
+
attr_reader item: untyped
|
|
401
|
+
attr_reader errors: Array[String]
|
|
402
|
+
def self.from: (Hash[String, untyped] hash) -> BulkFailure
|
|
403
|
+
end
|
|
404
|
+
class BulkResult
|
|
405
|
+
attr_reader items: Array[Hash[String, untyped]]
|
|
406
|
+
attr_reader failures: Array[BulkFailure]
|
|
407
|
+
attr_reader async: bool
|
|
408
|
+
def self.from: (Integer status, untyped body, String? key) -> BulkResult
|
|
409
|
+
def async?: () -> bool
|
|
410
|
+
def success?: () -> bool
|
|
411
|
+
end
|
|
283
412
|
class Purchase
|
|
284
413
|
attr_reader id: Integer?
|
|
285
414
|
attr_reader transaction_id: String?
|
|
@@ -309,6 +438,7 @@ module Kit
|
|
|
309
438
|
attr_reader delay_unit: String?
|
|
310
439
|
attr_reader send_days: Array[untyped]?
|
|
311
440
|
attr_reader stats: Hash[String, untyped]?
|
|
441
|
+
attr_reader content: String?
|
|
312
442
|
def self.from: (Hash[String, untyped] hash) -> SequenceEmail
|
|
313
443
|
end
|
|
314
444
|
class BroadcastStats
|
|
@@ -385,12 +515,22 @@ module Kit
|
|
|
385
515
|
attr_reader canceled_at: String?
|
|
386
516
|
attr_reader location: Hash[String, untyped]?
|
|
387
517
|
attr_reader fields: Hash[String, untyped]?
|
|
518
|
+
attr_reader added_at: String?
|
|
519
|
+
attr_reader tagged_at: String?
|
|
520
|
+
attr_reader referrer: String?
|
|
521
|
+
attr_reader referrer_utm_parameters: Hash[String, untyped]?
|
|
522
|
+
attr_reader attribution: Hash[String, untyped]?
|
|
523
|
+
attr_reader tags: Array[Hash[String, untyped]]?
|
|
524
|
+
attr_reader tag_names: Array[String]?
|
|
525
|
+
attr_reader tag_ids: Array[Integer]?
|
|
526
|
+
attr_reader stats: Hash[String, untyped]?
|
|
388
527
|
def self.from: (Hash[String, untyped] hash) -> Subscriber
|
|
389
528
|
end
|
|
390
529
|
end
|
|
391
530
|
|
|
392
531
|
module Resources
|
|
393
532
|
class Base
|
|
533
|
+
OMIT: Object
|
|
394
534
|
def initialize: (Connection connection) -> void
|
|
395
535
|
end
|
|
396
536
|
class Account < Base
|
|
@@ -406,11 +546,12 @@ module Kit
|
|
|
406
546
|
def get: (Integer id) -> Objects::Subscriber
|
|
407
547
|
def create: (email_address: String, ?first_name: String?, ?state: String?, ?fields: Hash[untyped, untyped]?) -> Objects::Subscriber
|
|
408
548
|
def update: (Integer id, ?first_name: String?, ?email_address: String?, ?fields: Hash[untyped, untyped]?) -> Objects::Subscriber
|
|
409
|
-
def unsubscribe: (Integer id) -> Objects::Subscriber
|
|
549
|
+
def unsubscribe: (Integer id) -> Objects::Subscriber?
|
|
410
550
|
def filter: (**untyped params) -> Collection[Objects::Subscriber]
|
|
411
551
|
def tags: (Integer id, **untyped params) -> Collection[Objects::Tag]
|
|
412
|
-
def stats: (Integer id) -> Objects::SubscriberStats
|
|
552
|
+
def stats: (Integer id, ?email_sent_after: String?, ?email_sent_before: String?) -> Objects::SubscriberStats
|
|
413
553
|
def set_location: (Integer id, location: Hash[untyped, untyped]) -> Objects::Subscriber
|
|
554
|
+
def update_location: (Integer id, location: Hash[untyped, untyped]) -> Objects::Subscriber
|
|
414
555
|
def remove_location: (Integer id) -> void
|
|
415
556
|
end
|
|
416
557
|
class Tags < Base
|
|
@@ -437,24 +578,24 @@ module Kit
|
|
|
437
578
|
end
|
|
438
579
|
class Sequences < Base
|
|
439
580
|
def list: (**untyped params) -> Collection[Objects::Sequence]
|
|
440
|
-
def get: (Integer id) -> Objects::Sequence
|
|
441
|
-
def create: (name: String,
|
|
442
|
-
def update: (Integer id,
|
|
581
|
+
def get: (Integer id, ?include: String?) -> Objects::Sequence
|
|
582
|
+
def create: (name: String, ?email_address: String?, ?email_template_id: Integer?, ?send_days: Array[String]?, ?send_hour: Integer?, ?time_zone: String?, ?active: bool?, ?repeat: bool?, ?hold: bool?, ?exclude_subscriber_sources: Array[Hash[Symbol | String, untyped]]?) -> Objects::Sequence
|
|
583
|
+
def update: (Integer id, ?name: String?, ?email_address: String?, ?email_template_id: Integer?, ?send_days: Array[String]?, ?send_hour: Integer?, ?time_zone: String?, ?active: bool?, ?repeat: bool?, ?hold: bool?, ?exclude_subscriber_sources: Array[Hash[Symbol | String, untyped]]?) -> Objects::Sequence
|
|
443
584
|
def delete: (Integer id) -> void
|
|
444
585
|
def subscribers: (Integer sequence_id, **untyped params) -> Collection[Objects::Subscriber]
|
|
445
586
|
def add_subscriber: (Integer sequence_id, Integer subscriber_id) -> Objects::Subscriber
|
|
446
587
|
def add_subscriber_by_email: (Integer sequence_id, email_address: String) -> Objects::Subscriber
|
|
447
588
|
def emails: (Integer sequence_id, **untyped params) -> Collection[Objects::SequenceEmail]
|
|
448
|
-
def email: (Integer sequence_id, Integer id) -> Objects::SequenceEmail
|
|
449
|
-
def create_email: (Integer sequence_id,
|
|
450
|
-
def update_email: (Integer sequence_id, Integer id,
|
|
589
|
+
def email: (Integer sequence_id, Integer id, ?include: String?) -> Objects::SequenceEmail
|
|
590
|
+
def create_email: (Integer sequence_id, subject: String, delay_value: Integer, delay_unit: String, ?preview_text: String?, ?content: String?, ?email_template_id: Integer?, ?published: bool?, ?send_days: Array[String]?, ?position: Integer?) -> Objects::SequenceEmail
|
|
591
|
+
def update_email: (Integer sequence_id, Integer id, ?subject: String?, ?delay_value: Integer?, ?delay_unit: String?, ?preview_text: String?, ?content: String?, ?email_template_id: Integer?, ?published: bool?, ?send_days: Array[String]?, ?position: Integer?) -> Objects::SequenceEmail
|
|
451
592
|
def delete_email: (Integer sequence_id, Integer id) -> void
|
|
452
593
|
end
|
|
453
594
|
class Broadcasts < Base
|
|
454
595
|
def list: (**untyped params) -> Collection[Objects::Broadcast]
|
|
455
596
|
def get: (Integer id) -> Objects::Broadcast
|
|
456
|
-
def create: (
|
|
457
|
-
def update: (Integer id,
|
|
597
|
+
def create: (?subject: String?, ?preview_text: String?, ?content: String?, ?description: String?, ?public: bool?, ?published_at: String?, ?send_at: String?, ?email_address: String?, ?email_template_id: Integer?, ?thumbnail_alt: String?, ?thumbnail_url: String?, ?subscriber_filter: Array[Hash[Symbol | String, untyped]]?) -> Objects::Broadcast
|
|
598
|
+
def update: (Integer id, ?subject: String?, ?preview_text: String?, ?content: String?, ?description: String?, ?public: bool?, ?published_at: String?, ?send_at: String?, ?email_address: String?, ?email_template_id: Integer?, ?thumbnail_alt: String?, ?thumbnail_url: String?, ?subscriber_filter: Array[Hash[Symbol | String, untyped]]?) -> Objects::Broadcast
|
|
458
599
|
def delete: (Integer id) -> void
|
|
459
600
|
def stats_list: (**untyped params) -> Collection[Objects::BroadcastStats]
|
|
460
601
|
def stats: (Integer id) -> Objects::BroadcastStats
|
|
@@ -470,23 +611,23 @@ module Kit
|
|
|
470
611
|
class Purchases < Base
|
|
471
612
|
def list: (**untyped params) -> Collection[Objects::Purchase]
|
|
472
613
|
def get: (Integer id) -> Objects::Purchase
|
|
473
|
-
def create: (
|
|
614
|
+
def create: (email_address: String, transaction_id: String, status: String, currency: String, ?transaction_time: String?, ?subtotal: Numeric?, ?tax: Numeric?, ?shipping: Numeric?, ?discount: Numeric?, ?total: Numeric?, ?products: Array[Hash[Symbol | String, untyped]]?) -> Objects::Purchase
|
|
474
615
|
end
|
|
475
616
|
class Bulk < Base
|
|
476
|
-
def create_subscribers: (Array[untyped] subscribers, ?callback_url: String?) ->
|
|
477
|
-
def create_custom_fields: (Array[untyped] custom_fields, ?callback_url: String?) ->
|
|
478
|
-
def update_custom_field_values: (Array[untyped] custom_field_values, ?callback_url: String?) ->
|
|
479
|
-
def add_subscribers_to_forms: (Array[untyped] additions, ?callback_url: String?) ->
|
|
480
|
-
def create_tags: (Array[untyped] tags, ?callback_url: String?) ->
|
|
481
|
-
def delete_tags: (Array[untyped] tags, ?callback_url: String?) ->
|
|
482
|
-
def tag_subscribers: (Array[untyped] taggings, ?callback_url: String?) ->
|
|
483
|
-
def remove_tag_subscribers: (Array[untyped] taggings, ?callback_url: String?) ->
|
|
617
|
+
def create_subscribers: (Array[untyped] subscribers, ?callback_url: String?) -> Objects::BulkResult
|
|
618
|
+
def create_custom_fields: (Array[untyped] custom_fields, ?callback_url: String?) -> Objects::BulkResult
|
|
619
|
+
def update_custom_field_values: (Array[untyped] custom_field_values, ?callback_url: String?) -> Objects::BulkResult
|
|
620
|
+
def add_subscribers_to_forms: (Array[untyped] additions, ?callback_url: String?) -> Objects::BulkResult
|
|
621
|
+
def create_tags: (Array[untyped] tags, ?callback_url: String?) -> Objects::BulkResult
|
|
622
|
+
def delete_tags: (Array[untyped] tags, ?callback_url: String?) -> Objects::BulkResult
|
|
623
|
+
def tag_subscribers: (Array[untyped] taggings, ?callback_url: String?) -> Objects::BulkResult
|
|
624
|
+
def remove_tag_subscribers: (Array[untyped] taggings, ?callback_url: String?) -> Objects::BulkResult
|
|
484
625
|
end
|
|
485
626
|
class Snippets < Base
|
|
486
627
|
def list: (**untyped params) -> Collection[Objects::Snippet]
|
|
487
628
|
def get: (Integer id) -> Objects::Snippet
|
|
488
|
-
def create: (
|
|
489
|
-
def update: (Integer id,
|
|
629
|
+
def create: (name: String, snippet_type: String, ?content: String?, ?document_attributes: Hash[Symbol | String, untyped]?) -> Objects::Snippet
|
|
630
|
+
def update: (Integer id, ?name: String?, ?archived: bool?, ?content: String?, ?document_attributes: Hash[Symbol | String, untyped]?) -> Objects::Snippet
|
|
490
631
|
end
|
|
491
632
|
class Segments < Base
|
|
492
633
|
def list: (**untyped params) -> Collection[Objects::Segment]
|
|
@@ -500,6 +641,7 @@ module Kit
|
|
|
500
641
|
def list: (**untyped params) -> Collection[Objects::WebhookEndpoint]
|
|
501
642
|
def get: (Integer id) -> Objects::WebhookEndpoint
|
|
502
643
|
def create: (url: String, events: Array[untyped], ?name: String?, ?description: String?) -> Objects::WebhookEndpoint
|
|
644
|
+
def update: (Integer id, ?name: String?, ?url: String?, ?description: String?, ?status: String?, ?events: Array[String]?) -> Objects::WebhookEndpoint
|
|
503
645
|
def delete: (Integer id) -> void
|
|
504
646
|
def rotate_secret: (Integer id, ?force: bool?) -> Objects::WebhookEndpoint
|
|
505
647
|
def revoke_previous_secret: (Integer id) -> Objects::WebhookEndpoint
|
|
@@ -508,7 +650,7 @@ module Kit
|
|
|
508
650
|
|
|
509
651
|
class Client
|
|
510
652
|
attr_reader config: Configuration
|
|
511
|
-
def initialize: (?api_key: String?, ?access_token: String?,
|
|
653
|
+
def initialize: (?api_key: String?, ?access_token: String?, ?base_url: String, ?open_timeout: Integer, ?read_timeout: Integer, ?write_timeout: Integer, ?max_retries: Integer, ?retry_backoff: Float, ?max_backoff: Numeric) -> void
|
|
512
654
|
def account: () -> Resources::Account
|
|
513
655
|
def subscribers: () -> Resources::Subscribers
|
|
514
656
|
def tags: () -> Resources::Tags
|