Add webhook endpoints#108
Conversation
📝 WalkthroughWalkthroughThis PR adds a complete Webhooks API implementation to the Mailtrap Ruby client, including a new ChangesWebhooks API Feature
Sequence DiagramsequenceDiagram
participant Client
participant WebhooksAPI
participant HTTP
participant MailtrapAPI as Mailtrap API
participant Response
Client->>WebhooksAPI: create(url, webhook_type, ...)
WebhooksAPI->>WebhooksAPI: wrap options in { webhook: ... }
WebhooksAPI->>HTTP: POST /api/accounts/{id}/webhooks
HTTP->>MailtrapAPI: request with JSON body
MailtrapAPI->>HTTP: 200 OK + webhook JSON
HTTP->>WebhooksAPI: response body
WebhooksAPI->>Response: map to Webhook entity
Response-->>Client: Webhook instance
Client->>WebhooksAPI: list()
WebhooksAPI->>HTTP: GET /api/accounts/{id}/webhooks
HTTP->>MailtrapAPI: request
MailtrapAPI->>HTTP: 200 OK + data array
HTTP->>WebhooksAPI: response body
WebhooksAPI->>Response: map each item to Webhook
Response-->>Client: Array<Webhook>
Client->>WebhooksAPI: delete(webhook_id)
WebhooksAPI->>HTTP: DELETE /api/accounts/{id}/webhooks/{id}
HTTP->>MailtrapAPI: request
MailtrapAPI->>HTTP: 200 OK + deleted webhook JSON
HTTP->>WebhooksAPI: response body
WebhooksAPI->>Response: convert to Webhook or return nil
Response-->>Client: Webhook or nil
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with 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.
Inline comments:
In `@lib/mailtrap/webhooks_api.rb`:
- Around line 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.
In
`@spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_create/maps_response_data_to_Webhook_object.yml`:
- Around line 69-71: The recorded cassette contains a plaintext secret under the
key "signing_secret" in the response body string; update the fixture used by the
test (the YAML cassette that maps response data to Webhook objects) to replace
the actual secret value with a sanitized placeholder like
"<REDACTED_SIGNING_SECRET>" and adjust any expectations in specs to assert
presence or format rather than exact secret value; ensure the change targets the
"signing_secret" entry inside the body string in the cassette so tests still
validate webhook mapping without storing sensitive data.
In `@spec/spec_helper.rb`:
- Line 28: The current masking call interaction.response.body.gsub! only matches
compact JSON and misses formatted variants; update the gsub! invocation that
targets "signing_secret" to use a regex that allows optional whitespace around
the colon (e.g. match "signing_secret"\s*:\s*"...") so both compact and
pretty-printed responses are redacted; keep the same replacement string
("signing_secret":"my-signing-secret") so fixtures remain consistent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 438cad09-4460-4fcb-93e3-b76c6aff1696
📒 Files selected for processing (18)
README.mdexamples/webhooks_api.rblib/mailtrap.rblib/mailtrap/webhook.rblib/mailtrap/webhooks_api.rbspec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_create/maps_response_data_to_Webhook_object.ymlspec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_create/when_API_returns_an_error/raises_a_Mailtrap_Error.ymlspec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_delete/returns_deleted_Webhook.ymlspec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_delete/when_webhook_does_not_exist/raises_not_found_error.ymlspec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_get/maps_response_data_to_Webhook_object.ymlspec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_get/when_webhook_does_not_exist/raises_not_found_error.ymlspec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_list/maps_response_data_to_Webhook_objects.ymlspec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_list/when_api_key_is_incorrect/raises_authorization_error.ymlspec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_update/maps_response_data_to_Webhook_object.ymlspec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_update/when_webhook_does_not_exist/raises_not_found_error.ymlspec/mailtrap/webhook_spec.rbspec/mailtrap/webhooks_api_spec.rbspec/spec_helper.rb
| # @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 |
There was a problem hiding this comment.
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])🤖 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.
| body: | ||
| encoding: ASCII-8BIT | ||
| string: '{"data":{"signing_secret":"my-signing-secret","id":3080,"url":"https://example.com/mailtrap/webhooks","active":true,"webhook_type":"email_sending","payload_format":"json","sending_stream":"transactional","domain_id":null,"event_types":["delivery","bounce"]}}' |
There was a problem hiding this comment.
Redact signing_secret in recorded response bodies.
Line 71 stores a secret-like value directly in the cassette. Please replace it with a sanitized placeholder and rely on presence-based assertions in specs.
🔒 Suggested fixture hardening
- string: '{"data":{"signing_secret":"my-signing-secret","id":3080,"url":"https://example.com/mailtrap/webhooks","active":true,"webhook_type":"email_sending","payload_format":"json","sending_stream":"transactional","domain_id":null,"event_types":["delivery","bounce"]}}'
+ string: '{"data":{"signing_secret":"<SIGNING_SECRET>","id":3080,"url":"https://example.com/mailtrap/webhooks","active":true,"webhook_type":"email_sending","payload_format":"json","sending_stream":"transactional","domain_id":null,"event_types":["delivery","bounce"]}}'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| body: | |
| encoding: ASCII-8BIT | |
| string: '{"data":{"signing_secret":"my-signing-secret","id":3080,"url":"https://example.com/mailtrap/webhooks","active":true,"webhook_type":"email_sending","payload_format":"json","sending_stream":"transactional","domain_id":null,"event_types":["delivery","bounce"]}}' | |
| body: | |
| encoding: ASCII-8BIT | |
| string: '{"data":{"signing_secret":"<SIGNING_SECRET>","id":3080,"url":"https://example.com/mailtrap/webhooks","active":true,"webhook_type":"email_sending","payload_format":"json","sending_stream":"transactional","domain_id":null,"event_types":["delivery","bounce"]}}' |
🤖 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
`@spec/fixtures/vcr_cassettes/Mailtrap_WebhooksAPI/_create/maps_response_data_to_Webhook_object.yml`
around lines 69 - 71, The recorded cassette contains a plaintext secret under
the key "signing_secret" in the response body string; update the fixture used by
the test (the YAML cassette that maps response data to Webhook objects) to
replace the actual secret value with a sanitized placeholder like
"<REDACTED_SIGNING_SECRET>" and adjust any expectations in specs to assert
presence or format rather than exact secret value; ensure the change targets the
"signing_secret" entry inside the body string in the cassette so tests still
validate webhook mapping without storing sensitive data.
| interaction.request.uri.gsub!(%r{/organizations/\d+/}, '/organizations/2222222/') | ||
| interaction.response.body.gsub!(/"account_id":\d+/, '"account_id": 1111111') | ||
| interaction.response.body.gsub!(/"organization_id":\d+/, '"account_id": 2222222') | ||
| interaction.response.body.gsub!(/"signing_secret":"[^"]*"/, '"signing_secret":"my-signing-secret"') |
There was a problem hiding this comment.
Broaden signing_secret masking to handle whitespace variants.
Line 28 only redacts compact JSON. A formatted response like "signing_secret": "..." would bypass masking and leak into fixtures.
Suggested patch
- interaction.response.body.gsub!(/"signing_secret":"[^"]*"/, '"signing_secret":"my-signing-secret"')
+ interaction.response.body.gsub!(
+ /"signing_secret"\s*:\s*"[^"]*"/,
+ '"signing_secret":"my-signing-secret"'
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| interaction.response.body.gsub!(/"signing_secret":"[^"]*"/, '"signing_secret":"my-signing-secret"') | |
| interaction.response.body.gsub!( | |
| /"signing_secret"\s*:\s*"[^"]*"/, | |
| '"signing_secret":"my-signing-secret"' | |
| ) |
🤖 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 `@spec/spec_helper.rb` at line 28, The current masking call
interaction.response.body.gsub! only matches compact JSON and misses formatted
variants; update the gsub! invocation that targets "signing_secret" to use a
regex that allows optional whitespace around the colon (e.g. match
"signing_secret"\s*:\s*"...") so both compact and pretty-printed responses are
redacted; keep the same replacement string
("signing_secret":"my-signing-secret") so fixtures remain consistent.
| webhooks_api.create( | ||
| url: 'https://example.com/mailtrap/audit-log', | ||
| webhook_type: 'audit_log' | ||
| ) |
There was a problem hiding this comment.
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
Motivation
Changes
Webhooks — email-sending.openapi.yml (pending mailtrap/mailtrap-openapi#28)
Summary by CodeRabbit
New Features
Documentation
Tests