citricguy/postmark-webhooks-laravel
Receive Postmark webhooks in Laravel with a simple endpoint and one event to handle payloads your way. No migrations or models. Configurable route and middleware verifies requests come from Postmark. Supports Laravel 12/13 and PHP 8.3+.
Installation
composer require citricguy/postmark-webhooks-laravel
Publish the config file:
php artisan vendor:publish --provider="Citricguy\PostmarkWebhooks\PostmarkWebhooksServiceProvider"
Configure Webhook Secret
Edit config/postmark-webhooks.php and set:
'secret' => env('POSTMARK_WEBHOOK_SECRET'),
Add to .env:
POSTMARK_WEBHOOK_SECRET=your_postmark_webhook_signature_secret
Define a Webhook Route
Add to routes/web.php:
Route::post('/postmark-webhook', [\Citricguy\PostmarkWebhooks\PostmarkWebhooksController::class, 'handle']);
First Use Case: Handling Delivered Emails
Create a listener for the delivered event:
php artisan make:listener HandleDeliveredEmail
Update app/Listeners/HandleDeliveredEmail.php:
public function handle(Delivered $event) {
Log::info('Email delivered:', ['message_id' => $event->messageId]);
}
Register the listener in EventServiceProvider.
Event-Based Processing
Delivered, Opened, Bounced).Validation & Security
X-Postmark-Signature header.Route::post('/postmark-webhook', [PostmarkWebhooksController::class, 'handle'])
->middleware('web', 'signed', 'throttle:60');
Dynamic Event Handling
Citricguy\PostmarkWebhooks\Events\PostmarkEvent for custom events:
class CustomEvent extends PostmarkEvent {
public function __construct(array $data) {
parent::__construct($data, 'custom_event');
}
}
PostmarkWebhooksServiceProvider:
$this->events['custom_event'] = \App\Events\CustomEvent::class;
Queueing Heavy Tasks
public function handle(Delivered $event) {
HandleEmailDelivery::dispatch($event->messageId);
}
Postmark Dashboard Setup Configure webhook URL in Postmark:
https://your-app.com/postmark-webhook
Select events (e.g., Delivered, Opened) and use the generated secret.
Testing Webhooks Use Postmark’s test mode or tools like webhook.site to simulate events.
Logging & Monitoring Log raw payloads for debugging:
Log::debug('Postmark webhook payload:', $event->payload);
Secret Mismatch
X-Postmark-Signature header fails validation, the request is rejected silently.Missing Events
Rate Limiting
throttle middleware or queue listeners.Payload Parsing
try {
// Process event
} catch (\Exception $e) {
Log::error('Webhook processing failed:', ['error' => $e->getMessage()]);
}
Enable Debug Logging
Add to config/postmark-webhooks.php:
'debug' => env('APP_DEBUG', false),
Logs raw payloads and validation steps.
Verify Headers
Check the X-Postmark-Signature and X-Postmark-Id headers in incoming requests:
dd(request()->header());
Custom Event Mapping
Override the default event mapping in PostmarkWebhooksServiceProvider:
protected function mapEvents(): array {
return [
'delivered' => Delivered::class,
// Override or add custom mappings
];
}
Middleware for Preprocessing Add middleware to modify payloads before event dispatch:
Route::post('/postmark-webhook', [PostmarkWebhooksController::class, 'handle'])
->middleware(\App\Http\Middleware\SanitizeWebhookPayload::class);
Webhook Retries
Implement retry logic for failed events using Laravel’s retry helper or a queue job:
public function handle(Failed $event) {
Retry::until(
fn() => $this->processFailedEmail($event),
maxAttempts: 3
);
}
How can I help you explore Laravel packages today?