McTaba Labs logo
Software EngineeringThe WhiteboardProjectsEventsMasterclassesWebinarsLearn
Book Call

500+ developers already in the community

Get answers, find accountability partners, and stay ahead

Join Discord →
McTaba Labs logo

Educating 1 million students in tech by 2030.

Programs

  • HighSchool Graduates Launchpad
  • AI & Business Automation
  • Corporate Training
  • McTaba Story & Mission
  • Full-Stack Curriculum
  • Course Pricing & Plans
  • Frequently Asked Questions

Resources

  • Join Discord Community
  • Tech Events in Nairobi
  • Masterclasses
  • Free Resources
  • Learn
  • Campus
  • Africa Tech Talent Report
  • Partner With Us
  • Start a Project
  • Our Work

Contact Us

  • 124 Manyani East Road, Lavington, Nairobi
  • +254727795603
  • Book a Discovery Call

Office Hours:

Mon-Fri: 9am - 2pm EAT

Sat: 10am - 12pm EAT

© 2027 McTaba Labs - A product of Edtech Industries. All rights reserved.

Privacy PolicyTerms of ServiceRefund Policy
  1. Learn
  2. Paystack for African Developers
  3. Handle Paystack Webhooks in Symfony
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 20, 2026

Handle Paystack Webhooks in Symfony

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.

In this article

  1. 1. Webhook Controller
  2. 2. Async Processing with Messenger
  3. 3. Testing
  4. 4. Ship Payments Faster

Webhook Controller

// 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;
        }
    }
}

Async Processing with Messenger

// 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.

Testing

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);
}

Ship Payments Faster

This guide is part of the Paystack integration guides by language and framework series.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Use $request->getContent() for the raw body bytes needed for HMAC verification.
  • ✓Use hash_hmac("sha512", $body, $secret) and hash_equals() for secure comparison.
  • ✓Symfony Messenger component works well for async webhook processing.
  • ✓Return Response with status 200 immediately before processing.
  • ✓Make handlers idempotent. Paystack retries failed deliveries.
  • ✓Symfony does not have global CSRF on controller actions, so no exemption needed.

Frequently Asked Questions

Does Symfony need CSRF exemption for webhooks?
No. Symfony does not apply CSRF protection to plain controller actions. CSRF is only used with Symfony Forms. Webhook endpoints work without any special configuration.
Should I use Messenger or Events for webhook processing?
Messenger is better for async processing with a queue transport. Events work for synchronous processing. For webhooks, Messenger with an async transport ensures fast responses.
How do I test webhooks locally?
Use ngrok to expose your Symfony dev server. Or compute the HMAC in your test and send it in the request header using the Symfony test client.

Learn Paystack Integration

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

Not ready? Create Free Account

Keep reading

Paystack Integration Guides by Language and Framework

A complete directory of Paystack integration guides organized by programming language and framework. Each guide covers accepting payments, verifying transactions, handling webhooks, and building subscriptions in your specific stack.

Read more

Accept Payments with Paystack in Svelte and SvelteKit

Accept Paystack payments in SvelteKit using server endpoints for backend logic and Svelte components for the frontend. This guide covers initializing transactions, inline popup checkout, redirect checkout, and verification in a single full-stack SvelteKit project.

Read more

Verify Paystack Payments in Flask

Verify Paystack payments server-side in your Flask application. This guide covers calling the Paystack verify endpoint, validating amounts and currencies, building reusable verification helpers, and preventing double-granting.

Read more

Build Paystack Subscriptions in ASP.NET Core

Implement Paystack recurring billing in ASP.NET Core. This guide covers creating plans, subscribing customers through transaction initialization, handling renewal webhooks with C#, and cancelling subscriptions.

Read more

Multi-Currency Checkout with Paystack

How to accept payments in NGN, GHS, KES, ZAR, and USD with Paystack. Covers currency detection, routing by customer location, currency-specific minimums, and how settlement works when you accept multiple currencies.

Read more

Paystack API Timeouts and Retry Strategy

Paystack API timeouts happen when your client gives up before Paystack responds. The fix: set a 15-30 second timeout, use AbortController, and only retry idempotent endpoints like verify. Never blindly retry transaction/initialize or you risk double charges.

Read more

Explore related topics

M-Pesa & African Stack

M-Pesa Daraja API tutorials, USSD development, and WhatsApp Business API guides. The African Stack that global bootcamps do not teach.

Learn to Code in Kenya

Everything a Kenyan needs to know before writing their first line of code. Career paths, real costs, hardware requirements, and honest answers about what it takes.

Learning to Code

Roadmaps, language guides, and practical advice for learning to code and launching a programming career.

Breaking Into Tech

Practical, no-nonsense guides for anyone looking to start or change their career in tech. From choosing your first language to acing interviews.

The African Stack: Skills for This Market

The skills that make Kenyan developers hard to replace: M-Pesa, USSD, WhatsApp, fintech, and building for the African market.

Bonaventure Ogeto

Bonaventure Ogeto

Founder, McTaba Labs

Bonaventure is the founder of McTaba Labs, a coding bootcamp in Nairobi, Kenya. He has spent years building products for the African market and mentoring the next generation of developers across the continent.