Auslösen von benutzerdefinierten Webhooks mit dem Event Dispatcher von TYPO3 (PSR-14)

Moderne TYPO3 Anwendungen müssen oft in Echtzeit mit externen Diensten kommunizieren. Ob Sie nun Headless-Setups erstellen, Daten mit APIs von Drittanbietern synchronisieren oder automatisierte Workflows auslösen, Webhooks sind unerlässlich, um Systeme miteinander zu verbinden.

Dieser Leitfaden bietet Ihnen eine bessere Lösung: die Verwendung des modernen PSR-14 Event Dispatchers von TYPO3. Anstatt veralteten Code oder benutzerdefinierte Hacks zu verwenden, lernen Sie, wie Sie Echtzeit-Webhook-Trigger einrichten, die saubere JSON-Daten an jeden externen Dienst senden - genau dann, wenn ein Datensatz in TYPO3 aktualisiert wird.

Was sind TYPO3 Webhooks?

TYPO3 Webhooks sind eine Möglichkeit für Ihre TYPO3 Website, Daten in Echtzeit an ein anderes System zu senden, wenn sich etwas ändert, z. B. wenn eine Seite aktualisiert wird, ein neuer Artikel veröffentlicht wird oder ein Datensatz gelöscht wird.

Das TYPO3 Content Management System (CMS) unterstützt Webhooks, die es Ihrer Website ermöglichen, automatisch Daten an ein anderes System zu senden, wenn etwas passiert - ohne jeglichen manuellen Aufwand.

Stellen Sie sich einen Webhook wie einen digitalen Boten vor. Wenn ein bestimmtes Ereignis auf Ihrer TYPO3-Website eintritt, z. B. wenn ein Benutzer ein Formular ausfüllt, kann ein Webhook ausgelöst werden, um diese Information an ein anderes System zu senden. Dieses System könnte sein:

  • Ein CRM (wie HubSpot oder Salesforce),
  • ein E-Mail-Dienst
  • Oder sogar eine Chat-App wie Slack oder Microsoft Teams.

Der Webhook sendet eine Nachricht (Payload genannt) in Echtzeit an eine bestimmte URL auf dem externen System. So kann TYPO3 Aufgaben automatisieren, Workflows auslösen und mit anderen Tools integrieren, ohne ständig nach Updates suchen oder Daten manuell pushen zu müssen.

Voraussetzungen zum Auslösen von Custom Webhooks

  • TYPO3 v10.4+ (PSR-14 Unterstützung)
  • Grundlegende PHP-Kenntnisse
  • Verständnis für die Entwicklung von TYPO3 Extensions
  • Vertrautheit mit JSON und HTTP-Anfragen

Der PSR-14-Ereignis-Dispatcher von TYPO3 im Überblick

Der PSR-14 Event Dispatcher ist das moderne Event-System von TYPO3, das in Version 10 die alten Hooks ersetzt hat. Es bietet eine standardisierte Möglichkeit, auf Ereignisse im TYPO3 Kern und in den Erweiterungen zu warten und darauf zu reagieren.

Warum PSR-14 im Vergleich zu Legacy Hooks?

  • Typsicherheit: Ereignisse sind PHP-Objekte mit definierten Eigenschaften
  • Bessere Leistung: Effizienter als String-basierte Hooks
  • Moderne Architektur: Entspricht den PSR-14-Standards
  • Zukunftssicher: Der empfohlene Ansatz für TYPO3 v10+

Einrichten Ihrer Erweiterung

Lassen Sie uns zunächst die grundlegende Struktur für unsere Webhook-Erweiterung erstellen.

Verzeichnisstruktur

ext_webhook/
├── Classes/
│   ├── Event/
│   │   └── WebhookEvent.php
│   ├── EventListener/
│   │   └── WebhookEventListener.php
│   └── Service/
│       └── WebhookService.php
├── Configuration/
│   └── Services.yaml
└── ext_emconf.php

Konfiguration der Erweiterung (ext_emconf.php)

<?php
$EM_CONF['ext_webhook'] = [
    'title' => 'TYPO3 Webhook Integration',
    'description' => 'Send webhooks on record updates using PSR-14',
    'category' => 'misc',
    'version' => '1.0.0',
    'state' => 'stable',
    'author' => 'Your Name',
    'author_email' => 'your.email@example.com',
    'constraints' => [
        'depends' => [
            'typo3' => '10.4.0-11.5.99',
        ],
    ],
];

Erstellen des Webhook-Ereignisses

Lassen Sie uns ein benutzerdefiniertes Ereignis erstellen, das unsere Webhook-Daten übertragen wird.

Klassen/Ereignis/WebhookEvent.php

<?php
declare(strict_types=1);

namespace Vendor\ExtWebhook\Event;

final class WebhookEvent
{
    private string $table;
    private int $uid;
    private array $recordData;
    private string $action;
    private string $webhookUrl;

    public function __construct(
        string $table,
        int $uid,
        array $recordData,
        string $action,
        string $webhookUrl
    ) {
        $this->table = $table;
        $this->uid = $uid;
        $this->recordData = $recordData;
        $this->action = $action;
        $this->webhookUrl = $webhookUrl;
    }

    public function getTable(): string
    {
        return $this->table;
    }

    public function getUid(): int
    {
        return $this->uid;
    }

    public function getRecordData(): array
    {
        return $this->recordData;
    }

    public function getAction(): string
    {
        return $this->action;
    }

    public function getWebhookUrl(): string
    {
        return $this->webhookUrl;
    }

    public function getPayload(): array
    {
        return [
            'event' => 'record_' . $this->action,
            'table' => $this->table,
            'uid' => $this->uid,
            'data' => $this->recordData,
            'timestamp' => time(),
        ];
    }
}

Erstellung des Webhook-Dienstes

Lassen Sie uns nun einen Dienst erstellen, der die eigentliche Webhook-Übertragung übernimmt.

Klassen/Service/WebhookService.php

<?php
declare(strict_types=1);

namespace Vendor\ExtWebhook\Service;

use TYPO3\CMS\Core\Http\RequestFactory;
use TYPO3\CMS\Core\Log\LogManager;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use Psr\Log\LoggerInterface;
use Vendor\ExtWebhook\Event\WebhookEvent;

class WebhookService
{
    private RequestFactory $requestFactory;
    private LoggerInterface $logger;

    public function __construct(RequestFactory $requestFactory, LogManager $logManager)
    {
        $this->requestFactory = $requestFactory;
        $this->logger = $logManager->getLogger(__CLASS__);
    }

    public function sendWebhook(WebhookEvent $event): bool
    {
        $url = $event->getWebhookUrl();
        $payload = $event->getPayload();

        try {
            $response = $this->requestFactory->request(
                $url,
                'POST',
                [
                    'headers' => [
                        'Content-Type' => 'application/json',
                        'User-Agent' => 'TYPO3-Webhook/1.0',
                    ],
                    'json' => $payload,
                    'timeout' => 30,
                ]
            );

            $statusCode = $response->getStatusCode();
            
            if ($statusCode >= 200 && $statusCode < 300) {
                $this->logger->info('Webhook sent successfully', [
                    'url' => $url,
                    'table' => $event->getTable(),
                    'uid' => $event->getUid(),
                    'status' => $statusCode,
                ]);
                return true;
            } else {
                $this->logger->warning('Webhook failed with HTTP error', [
                    'url' => $url,
                    'status' => $statusCode,
                    'response' => $response->getBody()->getContents(),
                ]);
                return false;
            }
        } catch (\Exception $e) {
            $this->logger->error('Webhook delivery failed', [
                'url' => $url,
                'error' => $e->getMessage(),
                'payload' => $payload,
            ]);
            return false;
        }
    }

    public function sendWebhookWithRetry(WebhookEvent $event, int $maxRetries = 3): bool
    {
        $attempt = 1;
        
        while ($attempt <= $maxRetries) {
            if ($this->sendWebhook($event)) {
                return true;
            }
            
            if ($attempt < $maxRetries) {
                $delay = pow(2, $attempt); // Exponential backoff
                sleep($delay);
                $this->logger->info("Retrying webhook delivery (attempt $attempt/$maxRetries)");
            }
            
            $attempt++;
        }
        
        $this->logger->error('Webhook delivery failed after all retries', [
            'url' => $event->getWebhookUrl(),
            'attempts' => $maxRetries,
        ]);
        
        return false;
    }
}

Erstellen des Ereignis-Listeners

Der Event-Listener wird auf die in TYPO3 eingebauten Events reagieren und unsere Webhooks auslösen.

Klassen/EventListener/WebhookEventListener.php

<?php
declare(strict_types=1);

namespace Vendor\ExtWebhook\EventListener;

use TYPO3\CMS\Core\DataHandling\Event\AfterRecordUpdatedEvent;
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use Vendor\ExtWebhook\Event\WebhookEvent;
use Vendor\ExtWebhook\Service\WebhookService;
use Psr\EventDispatcher\EventDispatcherInterface;

final class WebhookEventListener
{
    private WebhookService $webhookService;
    private EventDispatcherInterface $eventDispatcher;
    private array $configuration;

    public function __construct(
        WebhookService $webhookService,
        EventDispatcherInterface $eventDispatcher
    ) {
        $this->webhookService = $webhookService;
        $this->eventDispatcher = $eventDispatcher;
        
        // Load extension configuration
        $this->configuration = GeneralUtility::makeInstance(ExtensionConfiguration::class)
            ->get('ext_webhook') ?? [];
    }

    public function onAfterRecordUpdated(AfterRecordUpdatedEvent $event): void
    {
        $table = $event->getTable();
        $uid = $event->getUid();
        $recordData = $event->getRecord();

        // Check if webhooks are enabled for this table
        if (!$this->shouldSendWebhook($table)) {
            return;
        }

        // Get webhook URL from configuration
        $webhookUrl = $this->getWebhookUrl($table);
        if (empty($webhookUrl)) {
            return;
        }

        // Create and dispatch webhook event
        $webhookEvent = new WebhookEvent(
            $table,
            $uid,
            $recordData,
            'updated',
            $webhookUrl
        );

        // Send webhook asynchronously (or synchronously based on configuration)
        if ($this->configuration['async_webhooks'] ?? false) {
            // In a real implementation, you'd queue this for background processing
            // For now, we'll send it synchronously
            $this->webhookService->sendWebhookWithRetry($webhookEvent);
        } else {
            $this->webhookService->sendWebhookWithRetry($webhookEvent);
        }
    }

    private function shouldSendWebhook(string $table): bool
    {
        $enabledTables = GeneralUtility::trimExplode(
            ',',
            $this->configuration['enabled_tables'] ?? 'pages,tt_content',
            true
        );

        return in_array($table, $enabledTables, true);
    }

    private function getWebhookUrl(string $table): string
    {
        // You can customize this logic to support multiple URLs per table
        return $this->configuration['webhook_url'] ?? '';
    }
}

Registrierung von Diensten und Ereignis-Listenern

Konfigurieren Sie die Abhängigkeitsinjektion und die Ereignis-Listener.

Konfiguration/Dienste.yaml

services:
  _defaults:
    autowire: true
    autoconfigure: true
    public: false

  Vendor\ExtWebhook\:
    resource: '../Classes/*'

  Vendor\ExtWebhook\EventListener\WebhookEventListener:
    tags:
      - name: event.listener
        identifier: 'webhook-after-record-updated'
        method: 'onAfterRecordUpdated'
        event: TYPO3\CMS\Core\DataHandling\Event\AfterRecordUpdatedEvent

Konfiguration der Erweiterung

Konfigurationsoptionen in ext_conf_template.txt hinzufügen:

# cat=webhook/enable/10; type=string; label=Webhook URL: The URL to send webhooks to
webhook_url = https://example.com/webhook

# cat=webhook/tables/20; type=string; label=Enabled Tables: Comma-separated list of tables to monitor (e.g., pages,tt_content,news)
enabled_tables = pages,tt_content

# cat=webhook/performance/30; type=boolean; label=Async Webhooks: Send webhooks asynchronously (requires queue setup)
async_webhooks = 0

Testen Ihrer Webhook-Implementierung

1. Installieren und konfigurieren

  • Installieren Sie Ihre Erweiterung
  • Konfigurieren Sie die Webhook-URL in der Erweiterungskonfiguration
  • Richten Sie die Tabellen ein, die Sie überwachen wollen

2. Testen Sie die Webhook-Zustellung

Erstellen Sie einen einfachen Test-Endpunkt, um Webhooks zu empfangen:

// webhook-test.php
<?php
$json = file_get_contents('php://input');
$data = json_decode($json, true);

file_put_contents('webhook.log', date('Y-m-d H:i:s') . ": " . $json . "\n", FILE_APPEND);

http_response_code(200);
echo "OK";

3. Überprüfen in TYPO3

  • Bearbeiten Sie eine Seite oder ein Inhaltselement
  • Überprüfen Sie Ihre Webhook-Endpunkt-Protokolle
  • Überprüfen Sie die Struktur der JSON-Nutzdaten

Erweiterte Funktionen

Mehrere Webhook-URLs

Erweitern Sie die Konfiguration, um verschiedene URLs pro Tabelle zu unterstützen:

private function getWebhookUrl(string $table): string
{
    $urls = [
        'pages' => $this->configuration['webhook_url_pages'] ?? '',
        'tt_content' => $this->configuration['webhook_url_content'] ?? '',
        'default' => $this->configuration['webhook_url'] ?? '',
    ];

    return $urls[$table] ?? $urls['default'];
}

Webhook-Signaturen

Erhöhen Sie die Sicherheit, indem Sie Ihre Webhooks signieren:

Add security by signing your webhooks:
private function signPayload(array $payload, string $secret): string
{
    return hash_hmac('sha256', json_encode($payload), $secret);
}

Queue-Integration

Für Websites mit hohem Besucheraufkommen bietet sich die Integration mit dem Warteschlangensystem von TYPO3 an:

use TYPO3\CMS\Core\Messaging\AbstractMessage;
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageService;

// In your event listener
$this->queueService->add('webhook_queue', $webhookEvent);

Fehlersuche bei allgemeinen Problemen

Webhooks werden nicht ausgelöst

  • Überprüfen Sie die Ereignisregistrierung: Überprüfen der Services.yaml-Konfiguration
  • Tabellen-Überwachung: Sicherstellen, dass die Tabelle in enabled_tables enthalten ist
  • URL-Konfiguration: Überprüfen Sie, ob die Webhook-URL zugänglich ist

Performance-Probleme

  • Aktivieren Sie die asynchrone Verarbeitung: Setzen Sie async_webhooks = 1
  • Warteschlangen implementieren: Verwenden Sie das Warteschlangensystem von TYPO3 für hohes Volumen
  • Timeouts hinzufügen: Konfigurieren Sie geeignete HTTP-Timeouts

Tipps zur Fehlersuche

// Add debug logging
$this->logger->debug('Webhook triggered', [
    'table' => $table,
    'uid' => $uid,
    'url' => $webhookUrl,
]);

Bewährte Praktiken

  • Fehlerbehandlung: Implementieren Sie immer eine Wiederholungslogik und eine angemessene Fehlerbehandlung
  • Sicherheit: HTTPS verwenden und Webhook-Signaturen berücksichtigen
  • Leistung: Verwenden Sie asynchrone Verarbeitung für Websites mit hohem Datenaufkommen
  • Überwachung: Protokollieren Sie Webhook-Lieferungen zur Fehlersuche
  • Konfiguration: Webhook-URLs pro Umgebung konfigurierbar machen

Fazit

Der PSR-14 Event Dispatcher von TYPO3 bietet eine leistungsfähige, moderne Möglichkeit, Webhook-Funktionalität zu implementieren. Mit diesem Leitfaden haben Sie gelernt, wie man:

  • Benutzerdefinierte Events und Listener erstellen
  • Ein robustes Webhook-Zustellungssystem aufzubauen
  • Fehler und Wiederholungsversuche zuverlässig zu behandeln
  • Ihre Implementierung zu konfigurieren und zu testen

Dieser Ansatz ersetzt veraltete Hooks durch eine typsichere, performante Lösung, die perfekt für Headless TYPO3-Setups und Integrationen von Drittanbietern geeignet ist.

Nächste Schritte

  • Andere PSR-14-Ereignisse für verschiedene Auslöser erforschen
  • Warteschlangenbasierte asynchrone Verarbeitung implementieren
  • Webhook-Signaturüberprüfung hinzufügen
  • Erstellen eines Backend-Moduls für die Webhook-Verwaltung

Der PSR-14 Event Dispatcher eröffnet unendlich viele Möglichkeiten, die Funktionalität von TYPO3 zu erweitern. Experimentieren Sie mit verschiedenen Ereignissen und bauen Sie die Integrationen, die Ihre Projekte benötigen!

Post a Comment

×

    Got answer to the question you were looking for?