Class: Webhooks::V1::SendgridController

Inherits:
BaseController
  • Object
show all
Defined in:
app/controllers/webhooks/v1/sendgrid_controller.rb

Overview

SendGrid Event Webhook Controller
Reference: https://www.twilio.com/docs/sendgrid/for-developers/tracking-events/event

SendGrid sends batches of events in a single POST request as a JSON array.
Each event contains:

  • email: recipient email address
  • timestamp: Unix timestamp of event
  • event: event type (processed, delivered, open, click, bounce, etc.)
  • unique_id/sg_message_id: message identifier
  • additional fields depending on event type

Security:

Multiple SendGrid Subusers:

  • Each subuser has its own webhook verification key
  • URL includes ?u=<subuser_name> to identify which key to use
  • Keys stored in credentials: sendgrid_api.<subuser_name>.webhook_verification_key
  • Example: /webhooks/v1/sendgrid?u=warmlyyours_transaction

This controller ingests each event into WebhookLog for async processing,
providing full audit trail and retry capabilities.

Instance Method Summary collapse

Instance Method Details

#createObject

POST /webhooks/v1/sendgrid?u=



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'app/controllers/webhooks/v1/sendgrid_controller.rb', line 33

def create
  raw_payload = request.raw_post
  events = parse_events(raw_payload)

  if events.blank?
    Rails.logger.warn "[SendGrid Webhook] Empty or invalid payload received (subuser: #{sendgrid_subuser})"
    return head :ok # Return OK to prevent SendGrid from retrying empty payloads
  end

  Rails.logger.info "[SendGrid Webhook] Received #{events.size} event(s) from subuser: #{sendgrid_subuser}"

  # Ingest each event into WebhookLog
  # Sort by timestamp to process oldest first
  events.sort_by { |evt| evt['timestamp'] || 0 }.each do |event_data|
    ingest_event(event_data)
  end

  head :ok
rescue JSON::ParserError => e
  Rails.logger.error "[SendGrid Webhook] Failed to parse JSON payload: #{e.message}"
  head :bad_request
rescue RedisClient::CannotConnectError => e
  Rails.logger.error "[SendGrid Webhook] Redis unavailable, returning 503 for retry: #{e.message}"
  head :service_unavailable
rescue StandardError => e
  Rails.logger.error "[SendGrid Webhook] Error processing webhook: #{e.message}"
  head :internal_server_error
end