ReachScore Docs

Webhooks

Receive real-time notifications when events happen in your ReachScore account instead of polling the API.

Why use webhooks? Instead of repeatedly polling the API to check if a test has completed, webhooks push events to your server the moment they happen. This is more efficient and provides faster response times.

How Webhooks Work

1

Register an Endpoint

Create a webhook endpoint in your dashboard or via API, specifying the events you want to receive.

2

Receive Events

When an event occurs, we send a POST request to your endpoint with the event payload.

3

Acknowledge Receipt

Return a 2xx status code within 30 seconds to acknowledge receipt. We'll retry on failure.

Creating a Webhook Endpoint

Register a webhook endpoint to start receiving events:

curl -X POST https://api.reachscore.co/v1/webhooks/endpoints \
  -H "Authorization: Bearer $REACHSCORE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/reachscore",
    "events": ["test.completed", "test.failed", "domain.verified"],
    "description": "Production webhook handler"
  }'

The response includes a secret which you must store securely for signature verification:

{
  "id": "whe_7xK2mN9pQrT4v",
  "url": "https://your-server.com/webhooks/reachscore",
  "events": ["test.completed", "test.failed", "domain.verified"],
  "secret": "whsec_abc123xyz...",
  "status": "active",
  "created_at": "2024-01-15T10:30:00Z"
}

Available Events

EventDescription
test.completedA deliverability test finished successfully with results
test.failedA test failed (email not received within timeout)
domain.verifiedDomain ownership was successfully verified
domain.verification_failedDomain verification check failed
monitor.alertA scheduled monitor triggered an alert condition
alert.triggeredAn alert rule condition was met
alert.resolvedA previously triggered alert has resolved

Event Payload Format

All webhook payloads follow a consistent structure:

{
  "id": "evt_8nL3pR2qSsU5w",
  "object": "event",
  "type": "test.completed",
  "created_at": "2024-01-15T10:31:15Z",
  "livemode": true,
  "data": {
    "id": "test_7xK2mN9pQrT4v",
    "object": "test",
    "status": "completed",
    "score": 92,
    "grade": "A",
    "from_address": "notifications@yourcompany.com",
    "auth_results": {
      "spf": "pass",
      "dkim": "pass",
      "dmarc": "pass"
    },
    "inbox_placement": {
      "gmail": "inbox",
      "outlook": "inbox"
    },
    "completed_at": "2024-01-15T10:31:15Z"
  }
}

Verifying Webhook Signatures

Every webhook request includes a signature header that you should verify to ensure the request came from ReachScore. The signature is computed using HMAC-SHA256 with your endpoint's secret.

Request Headers

HeaderDescription
X-ReachScore-SignatureHMAC-SHA256 signature of the payload
X-ReachScore-TimestampUnix timestamp when the webhook was sent
X-ReachScore-Event-IDUnique identifier for the event

Verification Example (Node.js)

import crypto from 'crypto';

function verifyWebhookSignature(payload, signature, timestamp, secret) {
  // Check timestamp is within 5 minutes
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) {
    throw new Error('Timestamp too old');
  }

  // Compute expected signature
  const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  // Compare signatures using timing-safe comparison
  const expected = Buffer.from(expectedSignature, 'hex');
  const received = Buffer.from(signature.replace('sha256=', ''), 'hex');

  if (!crypto.timingSafeEqual(expected, received)) {
    throw new Error('Invalid signature');
  }

  return true;
}

// In your webhook handler
app.post('/webhooks/reachscore', (req, res) => {
  try {
    verifyWebhookSignature(
      req.body,
      req.headers['x-reachscore-signature'],
      req.headers['x-reachscore-timestamp'],
      process.env.WEBHOOK_SECRET
    );

    // Process the event
    const event = req.body;
    switch (event.type) {
      case 'test.completed':
        handleTestCompleted(event.data);
        break;
      // ... handle other events
    }

    res.status(200).send('OK');
  } catch (error) {
    res.status(400).send('Invalid signature');
  }
});

Verification Example (Python)

import hmac
import hashlib
import time
import json

def verify_webhook_signature(payload, signature, timestamp, secret):
    # Check timestamp is within 5 minutes
    now = int(time.time())
    if abs(now - int(timestamp)) > 300:
        raise ValueError("Timestamp too old")

    # Compute expected signature
    signed_payload = f"{timestamp}.{json.dumps(payload)}"
    expected_signature = hmac.new(
        secret.encode(),
        signed_payload.encode(),
        hashlib.sha256
    ).hexdigest()

    # Compare signatures
    received = signature.replace("sha256=", "")
    if not hmac.compare_digest(expected_signature, received):
        raise ValueError("Invalid signature")

    return True

Retry Behavior

If your endpoint fails to respond with a 2xx status code within 30 seconds, we retry with exponential backoff:

Retry Schedule
  • Attempt 1: Immediate
  • Attempt 2: After 1 minute
  • Attempt 3: After 5 minutes
  • Attempt 4: After 30 minutes
  • Attempt 5: After 2 hours
  • Attempt 6: After 8 hours
Timeout & Failures
  • 30 second timeout per attempt
  • 6 total attempts over ~10 hours
  • Events marked as failed after all retries
  • View failed deliveries in dashboard

Best Practices

Return quickly

Process webhooks asynchronously. Return a 200 immediately and queue the work for background processing.

Handle duplicates

Use the event ID for idempotency. The same event may be delivered more than once during retries.

Always verify signatures

Never process webhook payloads without verifying the signature first.

Use HTTPS

Webhook endpoints must use HTTPS. We do not send webhooks to HTTP URLs.

Testing Webhooks Locally

Use a service like ngrok to expose your local development server to receive webhooks. Create a tunnel with ngrok http 3000 and use the generated URL as your webhook endpoint.