Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ Email API:
- Sending Domains API – [`sending_domains_api.rb`](examples/sending_domains_api.rb)
- Sending Stats API – [`stats_api.rb`](examples/stats_api.rb)
- Email Logs API – [`email_logs_api.rb`](examples/email_logs_api.rb)
- Webhooks API – [`webhooks_api.rb`](examples/webhooks_api.rb)

Email Sandbox (Testing):

Expand Down
42 changes: 42 additions & 0 deletions examples/webhooks_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
require 'mailtrap'

account_id = 3229
client = Mailtrap::Client.new(api_key: 'your-api-key')
webhooks_api = Mailtrap::WebhooksAPI.new(account_id, client)

# Create an `email_sending` webhook
webhook = webhooks_api.create(
url: 'https://example.com/mailtrap/webhooks',
webhook_type: 'email_sending',
payload_format: 'json',
sending_stream: 'transactional',
event_types: %w[delivery bounce],
domain_id: 435
)
# => #<struct Mailtrap::Webhook id=1, url="https://example.com/mailtrap/webhooks", ..., signing_secret="a1b2c3...">

# Create an `audit_log` webhook
webhooks_api.create(
url: 'https://example.com/mailtrap/audit-log',
webhook_type: 'audit_log'
)
Comment on lines +19 to +22
Copy link
Copy Markdown
Contributor

@mklocek mklocek May 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw. now that we have Webhook API, we could also add helpers to the SDKs to verify webhooks using the signature. Basically what we have in code samples in https://docs.mailtrap.io/email-api-smtp/advanced/webhooks#verifying-the-signature


# List all webhooks
webhooks_api.list
# => [#<struct Mailtrap::Webhook id=1, ...>, #<struct Mailtrap::Webhook id=2, ...>]

# Get a single webhook
webhooks_api.get(webhook.id)
# => #<struct Mailtrap::Webhook id=1, ...>

# Update webhook
webhooks_api.update(
webhook.id,
active: false,
event_types: %w[delivery bounce unsubscribe]
)
# => #<struct Mailtrap::Webhook id=1, active=false, ...>

# Delete webhook
webhooks_api.delete(webhook.id)
# => #<struct Mailtrap::Webhook id=1, ...>
1 change: 1 addition & 0 deletions lib/mailtrap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
require_relative 'mailtrap/sandbox_messages_api'
require_relative 'mailtrap/sandbox_attachments_api'
require_relative 'mailtrap/stats_api'
require_relative 'mailtrap/webhooks_api'

module Mailtrap
# @!macro api_errors
Expand Down
30 changes: 30 additions & 0 deletions lib/mailtrap/webhook.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

module Mailtrap
# Data Transfer Object for Webhook
# @see https://api-docs.mailtrap.io/docs/mailtrap-api-docs/8d553c46c2d33-webhooks
# @attr_reader id [Integer] The webhook ID
# @attr_reader url [String] The URL that will receive webhook payloads
# @attr_reader active [Boolean] Whether the webhook is active
# @attr_reader webhook_type [String] The type of webhook (`email_sending` or `audit_log`)
# @attr_reader payload_format [String] The webhook payload format (`json` or `jsonlines`)
# @attr_reader sending_stream [String, nil] The sending stream (`transactional` or `bulk`).
# Applicable only for `email_sending` webhooks.
# @attr_reader domain_id [Integer, nil] The sending domain ID the webhook is scoped to,
# or nil for all domains. Applicable only for `email_sending` webhooks.
# @attr_reader event_types [Array<String>] The event types the webhook is subscribed to.
# Applicable only for `email_sending` webhooks.
# @attr_reader signing_secret [String, nil] HMAC SHA-256 signing secret. Returned only on creation.
Webhook = Struct.new(
:id,
:url,
:active,
:webhook_type,
:payload_format,
:sending_stream,
:domain_id,
:event_types,
:signing_secret,
keyword_init: true
)
end
87 changes: 87 additions & 0 deletions lib/mailtrap/webhooks_api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# frozen_string_literal: true

require_relative 'base_api'
require_relative 'webhook'

module Mailtrap
class WebhooksAPI
include BaseAPI

self.supported_options = %i[url webhook_type active payload_format sending_stream event_types domain_id]

self.response_class = Webhook

# Lists all webhooks for the account
# @return [Array<Webhook>] Array of webhooks
# @!macro api_errors
def list
response = client.get(base_path)
response[:data].map { |item| build_entity(item, response_class) }
end

# Retrieves a specific webhook
# @param webhook_id [Integer] The webhook ID
# @return [Webhook] Webhook object
# @!macro api_errors
def get(webhook_id)
base_get(webhook_id)
end

# Creates a new webhook
# @param [Hash] options The parameters to create
# @option options [String] :url The URL that will receive webhook payloads
# @option options [String] :webhook_type The type of webhook (`email_sending` or `audit_log`)
# @option options [Boolean] :active Whether the webhook is active. Defaults to true.
# @option options [String] :payload_format Payload format (`json` or `jsonlines`). Defaults to `json`.
# @option options [String] :sending_stream Sending stream (`transactional` or `bulk`).
# Required for `email_sending` webhook type.
# @option options [Array<String>] :event_types Event types to subscribe to.
# Required for `email_sending` webhook type.
# @option options [Integer] :domain_id Sending domain ID to scope the webhook to.
# Applicable only for `email_sending` webhooks.
# @return [Webhook] Created webhook (includes `signing_secret`)
# @!macro api_errors
# @raise [ArgumentError] If invalid options are provided
def create(options)
base_create(options)
end

# Updates an existing webhook
# @param webhook_id [Integer] The webhook ID
# @param [Hash] options The parameters to update
# @option options [String] :url The URL that will receive webhook payloads
# @option options [Boolean] :active Whether the webhook is active
# @option options [String] :payload_format Payload format (`json` or `jsonlines`)
# @option options [Array<String>] :event_types Event types to subscribe to.
# Applicable only for `email_sending` webhooks.
# @return [Webhook] Updated webhook
# @!macro api_errors
# @raise [ArgumentError] If invalid options are provided
def update(webhook_id, options)
base_update(webhook_id, options, %i[url active payload_format event_types])
end
Comment on lines +52 to +62
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Disallow :url in update option whitelist.

Line 61 currently permits :url, but this update flow should restrict to truly updatable fields. Please remove :url from both the whitelist and YARD docs for #update.

✅ Suggested contract-aligned patch
-    # `@option` options [String] :url The URL that will receive webhook payloads
     # `@option` options [Boolean] :active Whether the webhook is active
     # `@option` options [String] :payload_format Payload format (`json` or `jsonlines`)
     # `@option` options [Array<String>] :event_types Event types to subscribe to.
     #   Applicable only for `email_sending` webhooks.
@@
-      base_update(webhook_id, options, %i[url active payload_format event_types])
+      base_update(webhook_id, options, %i[active payload_format event_types])
As per coding guidelines, `spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_update/maps_response_data_to_Webhook_object.yml` should contain only supported updatable fields (example explicitly notes not `url`).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/mailtrap/webhooks_api.rb` around lines 52 - 62, The update method
currently allows :url—remove :url from the whitelist passed to base_update in
the update(webhook_id, options) method (so only %i[active payload_format
event_types] remain) and update the YARD docs for `#update` to remove the :url
option; also update the test fixture file
maps_response_data_to_Webhook_object.yml to only include supported updatable
fields (remove any url entries) to match the contract.


# Deletes a webhook
# @param webhook_id [Integer] The webhook ID
# @return [Webhook] Deleted webhook
# @!macro api_errors
def delete(webhook_id)
response = client.delete("#{base_path}/#{webhook_id}")
handle_response(response) if response
end

private

def base_path
"/api/accounts/#{account_id}/webhooks"
end

def wrap_request(options)
{ webhook: options }
end

def handle_response(response)
build_entity(response[:data], response_class)
end
end
end

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading