Class: OpenAI::Resources::Webhooks

Inherits:
Object
  • Object
show all
Defined in:
lib/openai/resources/webhooks.rb,
lib/openai/resources/webhooks/event_types.rb,
sig/openai/resources/webhooks.rbs,
sig/openai/resources/webhooks/event_types.rbs

Defined Under Namespace

Classes: EventTypes

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client:) ⇒ Webhooks

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns a new instance of Webhooks.

Parameters:



319
320
321
322
# File 'lib/openai/resources/webhooks.rb', line 319

def initialize(client:)
  @client = client
  @event_types = OpenAI::Resources::Webhooks::EventTypes.new(client: client)
end

Instance Attribute Details

#event_types ⇒ OpenAI::Resources::Webhooks::EventTypes (readonly)



7
8
9
# File 'lib/openai/resources/webhooks.rb', line 7

def event_types
  @event_types
end

Instance Method Details

#create(event_types:, name:, url:, request_options: {}) ⇒ OpenAI::Models::Webhooks::WebhookEndpointWithSecret

Creates a webhook endpoint for the authenticated project.

Parameters:

Returns:

See Also:



27
28
29
30
31
32
33
34
35
36
37
# File 'lib/openai/resources/webhooks.rb', line 27

def create(params)
  parsed, options = OpenAI::Webhooks::WebhookCreateParams.dump_request(params)
  @client.request(
    method: :post,
    path: "webhook_endpoints",
    body: parsed,
    model: OpenAI::Webhooks::WebhookEndpointWithSecret,
    security: {bearer_auth: true},
    options: options
  )
end

#delete(webhook_endpoint_id, request_options: {}) ⇒ OpenAI::Models::Webhooks::DeletedWebhookEndpoint

Deletes a webhook endpoint for the authenticated project.

Parameters:

  • webhook_endpoint_id (String) —

    The ID of the webhook endpoint to delete.

  • request_options (OpenAI::RequestOptions, Hash{Symbol=>Object}, nil)

Returns:

See Also:



135
136
137
138
139
140
141
142
143
# File 'lib/openai/resources/webhooks.rb', line 135

def delete(webhook_endpoint_id, params = {})
  @client.request(
    method: :delete,
    path: ["webhook_endpoints/%1$s", webhook_endpoint_id],
    model: OpenAI::Webhooks::DeletedWebhookEndpoint,
    security: {bearer_auth: true},
    options: params[:request_options]
  )
end

#list(after: nil, limit: nil, request_options: {}) ⇒ OpenAI::Internal::CursorPage<OpenAI::Models::Webhooks::WebhookEndpoint>

Returns webhook endpoints for the authenticated project in newest-first order.

Parameters:

  • after (String, nil) —

    ID of the last webhook endpoint from the previous page.

  • limit (Integer) —

    Maximum number of webhook endpoints to return. Defaults to 20.

  • request_options (OpenAI::RequestOptions, Hash{Symbol=>Object}, nil)

Returns:

See Also:



109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/openai/resources/webhooks.rb', line 109

def list(params = {})
  parsed, options = OpenAI::Webhooks::WebhookListParams.dump_request(params)
  query = OpenAI::Internal::Util.encode_query_params(parsed)
  @client.request(
    method: :get,
    path: "webhook_endpoints",
    query: query,
    page: OpenAI::Internal::CursorPage,
    model: OpenAI::Webhooks::WebhookEndpoint,
    security: {bearer_auth: true},
    options: options
  )
end

#retrieve(webhook_endpoint_id, request_options: {}) ⇒ OpenAI::Models::Webhooks::WebhookEndpoint

Retrieves a webhook endpoint for the authenticated project.

Parameters:

  • webhook_endpoint_id (String) —

    The ID of the webhook endpoint to retrieve.

  • request_options (OpenAI::RequestOptions, Hash{Symbol=>Object}, nil)

Returns:

See Also:



51
52
53
54
55
56
57
58
59
# File 'lib/openai/resources/webhooks.rb', line 51

def retrieve(webhook_endpoint_id, params = {})
  @client.request(
    method: :get,
    path: ["webhook_endpoints/%1$s", webhook_endpoint_id],
    model: OpenAI::Webhooks::WebhookEndpoint,
    security: {bearer_auth: true},
    options: params[:request_options]
  )
end

#rotate_secret(webhook_endpoint_id, keep_old_secret_active_for_24_hours: nil, request_options: {}) ⇒ OpenAI::Models::Webhooks::WebhookEndpointWithSecret

Rotates the signing secret for a webhook endpoint in the authenticated project.

Parameters:

  • webhook_endpoint_id (String) —

    The ID of the webhook endpoint whose signing secret will be rotated.

  • keep_old_secret_active_for_24_hours (Boolean) —

    Whether to keep the previous signing secret valid for 24 hours after rotation. Defaults to false, which invalidates the previous secret immediately.

  • request_options (OpenAI::RequestOptions, Hash{Symbol=>Object}, nil)

Returns:

See Also:



161
162
163
164
165
166
167
168
169
170
171
# File 'lib/openai/resources/webhooks.rb', line 161

def rotate_secret(webhook_endpoint_id, params = {})
  parsed, options = OpenAI::Webhooks::WebhookRotateSecretParams.dump_request(params)
  @client.request(
    method: :post,
    path: ["webhook_endpoints/%1$s/rotate_secret", webhook_endpoint_id],
    body: parsed,
    model: OpenAI::Webhooks::WebhookEndpointWithSecret,
    security: {bearer_auth: true},
    options: options
  )
end

#test_(webhook_endpoint_id, event_type:, request_options: {}) ⇒ OpenAI::Models::Webhooks::WebhookEndpointTestResult

Sends a sample event to a webhook endpoint for the authenticated project.

Parameters:

Returns:

See Also:



188
189
190
191
192
193
194
195
196
197
198
# File 'lib/openai/resources/webhooks.rb', line 188

def test_(webhook_endpoint_id, params)
  parsed, options = OpenAI::Webhooks::WebhookTestParams.dump_request(params)
  @client.request(
    method: :post,
    path: ["webhook_endpoints/%1$s/test", webhook_endpoint_id],
    body: parsed,
    model: OpenAI::Webhooks::WebhookEndpointTestResult,
    security: {bearer_auth: true},
    options: options
  )
end

#unwrap(payload, headers = {}, webhook_secret = ) ⇒ OpenAI::Models::Webhooks::BatchCancelledWebhookEvent, ...

Validates that the given payload was sent by OpenAI and parses the payload.

Parameters:

  • payload (String) —

    The raw webhook payload as a string

  • headers (Hash) (defaults to: {}) —

    The webhook headers

  • webhook_secret (String, nil) (defaults to: ) —

    The webhook secret (optional, will use client webhook secret or ENV if not provided)

Returns:

Raises:

  • (ArgumentError) —

    if signature verification fails



209
210
211
212
213
214
215
216
217
218
# File 'lib/openai/resources/webhooks.rb', line 209

def unwrap(
  payload,
  headers = {},
  webhook_secret = @client.webhook_secret || ENV["OPENAI_WEBHOOK_SECRET"]
)
  verify_signature(payload, headers, webhook_secret)

  parsed = JSON.parse(payload, symbolize_names: true)
  OpenAI::Internal::Type::Converter.coerce(OpenAI::Models::Webhooks::UnwrapWebhookEvent, parsed)
end

#update(webhook_endpoint_id, event_types: nil, name: nil, url: nil, request_options: {}) ⇒ OpenAI::Models::Webhooks::WebhookEndpoint

Updates a webhook endpoint for the authenticated project.

Parameters:

  • webhook_endpoint_id (String) —

    The ID of the webhook endpoint to update.

  • event_types (Array<Symbol, OpenAI::Models::Webhooks::WebhookUpdateParams::EventType>) —

    The complete set of event types that should trigger deliveries.

  • name (String) —

    A new human-readable name for the webhook endpoint.

  • url (String) —

    A new HTTPS URL that receives webhook deliveries.

  • request_options (OpenAI::RequestOptions, Hash{Symbol=>Object}, nil)

Returns:

See Also:



82
83
84
85
86
87
88
89
90
91
92
# File 'lib/openai/resources/webhooks.rb', line 82

def update(webhook_endpoint_id, params = {})
  parsed, options = OpenAI::Webhooks::WebhookUpdateParams.dump_request(params)
  @client.request(
    method: :post,
    path: ["webhook_endpoints/%1$s", webhook_endpoint_id],
    body: parsed,
    model: OpenAI::Webhooks::WebhookEndpoint,
    security: {bearer_auth: true},
    options: options
  )
end

#verify_signature(payload, headers, webhook_secret = , tolerance = 300) ⇒ Object

Validates whether or not the webhook payload was sent by OpenAI.

Parameters:

  • payload (String) —

    The webhook payload as a string

  • headers (Hash) —

    The webhook headers

  • webhook_secret (String, nil) (defaults to: ) —

    The webhook secret (optional, will use client webhook secret or ENV if not provided)

  • tolerance (Integer) (defaults to: 300) —

    Maximum age of the webhook in seconds (default: 300 = 5 minutes)

Raises:

  • (ArgumentError) —

    if the signature is invalid



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/openai/resources/webhooks.rb', line 228

def verify_signature(
  payload,
  headers,
  webhook_secret = @client.webhook_secret || ENV["OPENAI_WEBHOOK_SECRET"],
  tolerance = 300
)
  if webhook_secret.nil? || webhook_secret.strip.empty?
    raise(
      ArgumentError,
      "The webhook secret must either be set using the env var, OPENAI_WEBHOOK_SECRET, " \
        "or passed to this function"
    )
  end

  header_names = %w[webhook-signature webhook-timestamp webhook-id]
  normalized_headers = {}
  headers.each do |name, value|
    name = name.to_s.downcase.delete_prefix("http_").tr("_", "-")
    next unless header_names.include?(name)

    if normalized_headers.key?(name) && normalized_headers[name] != value
      raise ArgumentError, "Conflicting values for #{name} header"
    end

    normalized_headers[name] = value
  end

  signature_header, timestamp_header, webhook_id = normalized_headers.values_at(*header_names)

  if signature_header.nil?
    raise ArgumentError, "Missing required webhook-signature header"
  end

  if timestamp_header.nil?
    raise ArgumentError, "Missing required webhook-timestamp header"
  end

  if webhook_id.nil?
    raise ArgumentError, "Missing required webhook-id header"
  end

  # Validate timestamp to prevent replay attacks
  begin
    timestamp_seconds = timestamp_header.to_i
  rescue ArgumentError
    raise ArgumentError, "Invalid webhook timestamp format"
  end

  now = Time.now.to_i
  if now - timestamp_seconds > tolerance
    raise OpenAI::Errors::InvalidWebhookSignatureError, "Webhook timestamp is too old"
  end

  if timestamp_seconds > now + tolerance
    raise OpenAI::Errors::InvalidWebhookSignatureError, "Webhook timestamp is too new"
  end

  # Extract signatures from v1,<base64> format
  # The signature header can have multiple values, separated by spaces.
  # Each value is in the format v1,<base64>. We should accept if any match.
  signatures = signature_header.split.map do |part|
    part.start_with?("v1,") ? part[3..] : part
  end

  # Decode the secret if it starts with whsec_
  decoded_secret = if webhook_secret.start_with?("whsec_")
    webhook_secret[6..].unpack1("m0")
  else
    webhook_secret
  end

  raise ArgumentError, "The webhook secret must not be empty" if decoded_secret.empty?

  # Create the signed payload: {webhook_id}.{timestamp}.{payload}
  signed_payload = "#{webhook_id}.#{timestamp_header}.#{payload}"

  # Compute HMAC-SHA256 signature
  expected_signature = [OpenSSL::HMAC.digest("sha256", decoded_secret, signed_payload)].pack("m0")

  # Accept if any signature matches using timing-safe comparison
  return if signatures.any? { |signature| OpenSSL.secure_compare(expected_signature, signature) }

  raise(
    OpenAI::Errors::InvalidWebhookSignatureError,
    "The given webhook signature does not match the expected signature"
  )
end