Skip to main content
Webhooks send HTTP POST requests to your server when events occur—deliveries, bounces, opens, clicks, and more. Instead of polling the API, get notified instantly.

How It Works

  1. Register a webhook URL with Ark
  2. Subscribe to the events you care about
  3. Ark sends signed POST requests when events occur
  4. Your server verifies the signature, processes the event, and returns 200

Available Events

EventDescription
MessageSentEmail successfully delivered to recipient’s server
MessageDelayedTemporary delivery issue, will retry
MessageDeliveryFailedPermanent delivery failure (hard bounce)
MessageBouncedBounce notification received after delivery
MessageHeldEmail held for review
MessageLoadedRecipient opened the email
MessageLinkClickedRecipient clicked a link
DomainDNSErrorDNS configuration issue
See the sidebar for detailed payload documentation for each event.

Creating a Webhook

require "ark_email"

client = ArkEmail::Client.new

webhook = client.webhooks.create(
  name: "Production Webhook",
  url: "https://yourapp.com/webhooks/ark",
  events: ["MessageSent", "MessageBounced", "MessageDeliveryFailed"]
)

puts "Webhook created: #{webhook.data.id}"

Request Headers

Each webhook request includes these headers:
HeaderExample
Content-Typeapplication/json
X-Ark-SignatureeKnOE3Cwo84q3znFgh3FwS8aU7g+pW5t...
X-Ark-Signature-KID2a9fb525bafa5a29858b7e1b4a1c5455...

Security

Learn how to verify signatures using the X-Ark-Signature header

Payload Structure

All webhook events follow this structure:
{
  "event": "MessageSent",
  "timestamp": 1704672000.123456,
  "uuid": "abc123-def456-ghi789",
  "payload": {
    "message": {
      "id": 12345,
      "token": "abc123",
      "to": "[email protected]",
      "from": "[email protected]",
      "subject": "Welcome to our service",
      "tag": "onboarding",
      "metadata": {
        "user_id": "usr_123456",
        "campaign_id": "camp_789"
      }
    },
    "status": "Sent",
    "details": "250 OK"
  }
}
Use metadata for correlation: The metadata object contains the custom key-value pairs you attached when sending the email. Use it to look up related records in your database.

Handling Webhooks

class ArkWebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def create
    # 1. Verify signature (see Security page for full implementation)
    verify_signature!

    event = JSON.parse(request.body.read)

    # 2. Idempotency: skip if already processed
    return head :ok if already_processed?(event['uuid'])
    mark_as_processed!(event['uuid'])

    # 3. Process event (queue to background job for production)
    process_event(event)

    # 4. Return 200 quickly
    head :ok
  rescue => e
    head :unauthorized
  end

  private

  def already_processed?(uuid)
    Rails.cache.exist?("ark_webhook:#{uuid}")
  end

  def mark_as_processed!(uuid)
    Rails.cache.write("ark_webhook:#{uuid}", true, expires_in: 24.hours)
  end

  def process_event(event)
    case event['event']
    when 'MessageSent'
      # Update delivery status
    when 'MessageBounced', 'MessageDeliveryFailed'
      # Handle bounce - consider adding to suppression list
    when 'MessageLoaded'
      # Track open
    end
  end
end

Retry Policy

If your endpoint doesn’t return a 2xx status, Ark retries with increasing delays:
AttemptDelay
1Immediate
22 minutes
33 minutes
46 minutes
510 minutes
615 minutes
After 6 failed attempts, the webhook is marked as failed.
Return 200 immediately, then process asynchronously. This prevents timeouts and ensures reliable delivery.

Testing

Send a test event to verify your endpoint is working:
curl -X POST https://api.arkhq.io/v1/webhooks/{webhookId}/test \
  -H "Authorization: Bearer $ARK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"event": "MessageSent"}'

Security

Full signature verification implementation with JWKS fetching