To handle Paystack webhooks in Symfony, create a controller that reads the raw body with $request->getContent(), computes an HMAC SHA-512 hash, compares it to the X-Paystack-Signature header, and dispatches the event for processing. Return a 200 response.
// src/Controller/WebhookController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class WebhookController extends AbstractController
{
#[Route('/webhooks/paystack', methods: ['POST'])]
public function handlePaystack(Request $request): Response
{
$signature = $request->headers->get('X-Paystack-Signature', '');
if (!$signature) {
return new Response('Missing signature', 400);
}
$body = $request->getContent();
$secret = $this->getParameter('paystack_secret_key');
$computedHash = hash_hmac('sha512', $body, $secret);
if (!hash_equals($computedHash, $signature)) {
return new Response('Invalid signature', 400);
}
$event = json_decode($body, true);
// Process the event
$this->processEvent($event);
return new Response('OK', 200);
}
private function processEvent(array $event): void
{
$eventType = $event['event'] ?? '';
$data = $event['data'] ?? [];
switch ($eventType) {
case 'charge.success':
$reference = $data['reference'] ?? null;
if ($reference) {
// Call verifyAndFulfill
}
break;
case 'subscription.create':
// Handle new subscription
break;
case 'invoice.payment_failed':
// Handle failed renewal
break;
}
}
}
// src/Message/PaystackWebhookMessage.php
namespace App\Message;
class PaystackWebhookMessage
{
private array $event;
public function __construct(array $event)
{
$this->event = $event;
}
public function getEvent(): array
{
return $this->event;
}
}
// In the controller:
use Symfony\Component\Messenger\MessageBusInterface;
$this->bus->dispatch(new PaystackWebhookMessage($event));
return new Response('OK', 200);
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
public function testValidWebhook(): void
{
$payload = json_encode(['event' => 'charge.success', 'data' => ['reference' => 'test']]);
$signature = hash_hmac('sha512', $payload, 'sk_test_xxx');
$this->client->request('POST', '/webhooks/paystack', [], [], [
'HTTP_X_PAYSTACK_SIGNATURE' => $signature,
'CONTENT_TYPE' => 'application/json',
], $payload);
$this->assertResponseStatusCodeSame(200);
}
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course