Webhooks #

Configure webhook endpoints to track email events in real-time from SMTP providers.

Overview #

Webhook integration provides:

  • Real-time Tracking: Instant email event notifications
  • Multiple Providers: SendGrid, Mailgun, Postmark, Amazon SES, Brevo
  • Event Processing: Delivered, opened, clicked, bounced, complained, unsubscribed
  • Signature Verification: Secure webhook validation
  • Automatic Stats: Updates message statistics automatically

Database Structure #

Schema::create('campaign_webhook_endpoints', function($table) {
    $table->increments('id');
    $table->integer('organization_id')->unsigned();
    $table->string('provider'); // sendgrid, mailgun, postmark, ses, brevo
    $table->string('name');
    $table->string('url');
    $table->string('secret')->nullable();
    $table->boolean('is_active')->default(true);
    $table->timestamps();
});

Schema::create('campaign_webhook_events', function($table) {
    $table->increments('id');
    $table->integer('webhook_endpoint_id')->unsigned();
    $table->integer('message_id')->unsigned()->nullable();
    $table->integer('subscriber_id')->unsigned()->nullable();
    $table->string('event_type'); // sent, delivered, opened, clicked, etc.
    $table->json('payload');
    $table->timestamp('event_at');
    $table->timestamps();
});

Webhook Providers #

SendGrid #

// Webhook URL
https://yourdomain.com/api/campaign/webhook/sendgrid

// Events supported
- sent
- delivered
- open
- click
- bounce
- dropped
- spamreport
- unsubscribe

Mailgun #

// Webhook URL
https://yourdomain.com/api/campaign/webhook/mailgun

// Events supported
- delivered
- opened
- clicked
- bounced
- complained
- unsubscribed

Postmark #

// Webhook URL
https://yourdomain.com/api/campaign/webhook/postmark

// Events supported
- Delivery
- Open
- Click
- Bounce
- SpamComplaint

Amazon SES #

// Webhook URL (via SNS)
https://yourdomain.com/api/campaign/webhook/ses

// Events supported
- Delivery
- Open
- Click
- Bounce
- Complaint

Brevo (Sendinblue) #

// Webhook URL
https://yourdomain.com/api/campaign/webhook/brevo

// Events supported
- delivered
- opened
- clicked
- hard_bounce
- soft_bounce
- unsubscribed

Webhook Processing #

WebhookProcessor Class #

Location: Classes/WebhookProcessor.php

use Albrightlabs\Campaign\Classes\WebhookProcessor;

$processor = new WebhookProcessor();
$processor->processWebhook($provider, $payload);

Event Mapping #

public function processWebhook($provider, $payload)
{
    // Extract event data
    $eventData = $this->parseProviderPayload($provider, $payload);

    // Find message and subscriber
    $message = $this->findMessage($eventData);
    $subscriber = $this->findSubscriber($eventData);

    // Update statistics
    $this->updateStatistics($message, $subscriber, $eventData['event']);

    // Log event
    $this->logWebhookEvent($eventData);
}

Provider Configuration #

SendGrid Setup #

  1. Log into SendGrid Dashboard
  2. Navigate to Settings → Mail Settings → Event Webhook
  3. Configure:
    URL: https://yourdomain.com/api/campaign/webhook/sendgrid
    HTTP POST
    Select events: All

Mailgun Setup #

  1. Log into Mailgun Control Panel
  2. Navigate to Webhooks
  3. Add webhooks for each event type:
    URL: https://yourdomain.com/api/campaign/webhook/mailgun

Postmark Setup #

  1. Log into Postmark Dashboard
  2. Navigate to Servers → Webhooks
  3. Configure:
    Webhook URL: https://yourdomain.com/api/campaign/webhook/postmark
    Select events: All

Signature Verification #

SendGrid #

private function verifySignature($payload, $signature, $secret)
{
    $expected = base64_encode(hash_hmac(
        'sha256',
        $payload . $timestamp . $signature,
        $secret,
        true
    ));

    return hash_equals($expected, $signature);
}

Mailgun #

private function verifyMailgunSignature($token, $timestamp, $signature, $secret)
{
    $expected = hash_hmac('sha256', $timestamp . $token, $secret);
    return hash_equals($expected, $signature);
}

Statistics Updates #

Event Processing #

switch ($eventType) {
    case 'delivered':
        $message->count_delivered++;
        break;

    case 'opened':
        $message->count_read++;
        break;

    case 'clicked':
        $message->count_clicked++;
        break;

    case 'bounced':
        $message->count_bounced++;
        $subscriber->status_id = SubscriberStatus::STATUS_BOUNCED;
        break;

    case 'unsubscribed':
        $message->count_stop++;
        $subscriber->status_id = SubscriberStatus::STATUS_UNSUBSCRIBED;
        $subscriber->unsubscribed_at = now();
        break;
}

$message->save();
$subscriber->save();

Webhook Management #

Creating Endpoints #

use Albrightlabs\Campaign\Models\OrganizationWebhook;

$webhook = OrganizationWebhook::create([
    'organization_id' => $org->id,
    'provider' => 'sendgrid',
    'name' => 'SendGrid Production',
    'url' => 'https://yourdomain.com/api/campaign/webhook/sendgrid',
    'secret' => 'webhook-secret-key',
    'is_active' => true
]);

Testing Webhooks #

// Test webhook delivery
curl -X POST https://yourdomain.com/api/campaign/webhook/sendgrid \
  -H "Content-Type: application/json" \
  -d @test-payload.json

Viewing Logs #

// Get recent webhook events
$events = WebhookEvent::where('webhook_endpoint_id', $webhookId)
    ->orderBy('event_at', 'desc')
    ->limit(100)
    ->get();

Backend UI #

Webhook Settings #

Location: Settings → Webhooks

Backend::url('albrightlabs/campaign/webhooksettings')

Configuration Form #

# fields.yaml
fields:
    provider:
        label: Provider
        type: dropdown
        options:
            sendgrid: SendGrid
            mailgun: Mailgun
            postmark: Postmark
            ses: Amazon SES
            brevo: Brevo
    name:
        label: Webhook Name
        required: true
    url:
        label: Webhook URL
        disabled: true
        comment: Copy this URL to your provider's webhook settings
    secret:
        label: Webhook Secret
        comment: Used for signature verification
    is_active:
        label: Active
        type: switch

Troubleshooting #

Webhooks Not Triggering #

// Check webhook configuration
$webhook = OrganizationWebhook::find($id);
echo "Active: " . ($webhook->is_active ? 'Yes' : 'No');
echo "URL: " . $webhook->url;

// Test with provider's webhook tester
// Check firewall allows incoming webhooks
// Verify URL is publicly accessible

Statistics Not Updating #

// Check recent webhook events
$events = WebhookEvent::where('message_id', $messageId)
    ->orderBy('event_at', 'desc')
    ->get();

if ($events->isEmpty()) {
    // No events received - check provider config
}

// Manually rebuild stats
$message->rebuildStats();

Signature Verification Failures #

// Verify secret matches
$webhook = OrganizationWebhook::where('provider', $provider)->first();
echo "Secret: " . $webhook->secret;

// Check payload format
// Ensure timestamp within tolerance
// Verify signature algorithm matches provider

← Email Validation | Next: API Reference →