Skip to main content
Webhooks allow you to receive real-time HTTP notifications when events occur in Ark. Instead of polling the API, configure a webhook endpoint and Ark will send events to your server as they happen.

How Webhooks Work

  1. Configure a webhook - Use the Webhooks API to register your endpoint URL
  2. Select events - Choose which events you want to receive (or subscribe to all)
  3. Receive notifications - Ark sends POST requests to your endpoint when events occur
  4. Process events - Your server handles the webhook payload and returns a 200 response

Event Categories

Email Events

EventDescriptionSuppression
MessageSentEmail successfully delivered to recipient’s serverClears if exists
MessageDelayedTemporary delivery issue (soft bounce), will retryNo
MessageDeliveryFailedPermanent delivery failure (hard bounce)Automatic
MessageBouncedNDR received after initial delivery acceptanceManual only
MessageHeldEmail held for manual review (spam, suppression, etc.)No
Bounce suppression: MessageDeliveryFailed (hard bounce) automatically adds the recipient to the suppression list. MessageBounced (NDR received) does NOT automatically suppress - handle this webhook to manually add addresses to your suppression list via the API if desired.

Engagement Events

EventDescription
MessageLoadedRecipient opened the email (tracking pixel loaded)
MessageLinkClickedRecipient clicked a link in the email

Domain Events

EventDescription
DomainDNSErrorDNS configuration issue with your sending domain

Payload Structure

All webhook events follow the same structure:
{
  "event": "MessageSent",
  "timestamp": 1704672000.123456,
  "uuid": "abc123-def456-ghi789",
  "payload": {
    "message": {
      "id": 12345,
      "token": "abc123",
      "direction": "outgoing",
      "message_id": "<[email protected]>",
      "to": "[email protected]",
      "from": "[email protected]",
      "subject": "Welcome to our service",
      "timestamp": 1704672000.0,
      "spam_status": "NotSpam",
      "tag": "onboarding"
    },
    "status": "Sent",
    "details": "250 OK",
    "output": "Message accepted",
    "sent_with_ssl": true,
    "timestamp": 1704672000.123456,
    "time": 0.234
  }
}

Security

All webhooks are cryptographically signed using RSA-SHA256. Each request includes:
HeaderDescription
X-Ark-SignatureBase64-encoded RSA-SHA256 signature of the request body
X-Ark-Signature-KIDKey ID identifying which public key was used
Verify signatures by fetching the public key from:
GET https://mail.arkhq.io/.well-known/jwks.json

Signature Verification

See complete verification examples in Node.js, Python, and Ruby

Responding to Webhooks

Your endpoint should:
  1. Return 200 quickly - Respond within 5 seconds to acknowledge receipt
  2. Verify the signature - Always verify the X-Ark-Signature header
  3. Process asynchronously - Queue events for processing if needed
  4. Handle duplicates - Use the uuid field for idempotency
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()

@app.post('/webhooks/ark')
async def handle_webhook(request: Request):
    # Verify signature first (see full example in guide)
    signature = request.headers.get('X-Ark-Signature')
    body = await request.body()

    if not verify_signature(body, signature):
        raise HTTPException(status_code=401, detail='Invalid signature')

    event = await request.json()

    # Acknowledge receipt immediately
    # Process event asynchronously
    process_webhook_event(event['event'], event['payload'], event['uuid'])

    return {'status': 'ok'}

Retry Policy

If your endpoint doesn’t respond with a 2xx status code, Ark will retry delivery:
AttemptDelay
1Immediate
22 minutes
33 minutes
46 minutes
510 minutes
615 minutes
After 6 failed attempts, the webhook is marked as failed and won’t be retried.

Testing Webhooks

Use the test endpoint to send test events to your configured URL:
from ark import Ark

client = Ark()

result = client.webhooks.test("whk_abc123", event="MessageSent")

print(f"Success: {result.data.success}")
print(f"Status code: {result.data.status_code}")