Example: Use a ZendHQ monitoring webhook to send a Slack notification

One use case for ZendHQ monitoring webhooks is to send pro-active notifications to DevOps staff. A common integration is to send these to a shared notification channel such as a Slack channel, PagerDuty, etc.

This example demonstrates a webhook that will react to ZendHQ warning and critical events, and send a Slack notification on receipt. The webhook handler is written using PHP 8.1, and uses the following libraries:

  • maznz/slack, a library for sending Slack notifications from PHP. This example does not detail how to configure the Slack client, only sending messages.
  • a PSR-15 request handler responding to the webhook; it makes the assumption that previous middleware has parsed the JSON response into the "parsed body" of the request.
  • a PSR-17 HTTP response factory to send a 204 response on receipt.

The webhook handler itself:


<?php
declare(strict_types=1);
namespace App\Handler;
use DateTimeImmutable;
use Maknz\Slack\Client;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Throwable;

class MonitoringWebhookHandler implements RequestHandlerInterface
{
    private const SEVERITIES = [
        'warning',
        'critical',
    ];

    public function __construct(
        private Client $slack,
        private ResponseFactoryInterface $responseFactory,
    ) {
    }

    public function handle(ServerRequestInterface $request) : ResponseInterface
    {
        $response = $this->responseFactory->createResponse(204);
        $event    = $request->getParsedBody();

        if (! isset($event['severity']) || ! in_array($event['severity'], self::SEVERITIES, true)) {
            return $response;
        }

        $data = [
            'Type'       => $event['name'],
            'URL'        => $event['request']['url'],
            'Node'       => $event['request']['node_name'],
            'Request ID' => $event['request_id'],
            'Timestamp'  => (new DateTimeImmutable("@{$event['time_sec']}"))->format('c'),
        ];

        $fields = [];
        foreach ($data as $key => $value) {
            $fields[] = [
                'title' => $key,
                'value' => $value,
                'short' => false,
            ];
        }

        try {
            $this->slack
                ->to('#infra-php-issues')
                ->attach([
                    'fallback' => "Issue of type {$event['severity']} in PHP application",
                    'text'     => 'Issue in PHP application',
                    'color'    => match ($event['severity']) {
                        'warning'  => 'warning',
                        'critical' => 'danger',
                    },
                    'fields'   => $fields,
                ])
                ->send('New alert for PHP application');
        } catch (Throwable) {
        } finally {
            return $response;
        }
    }
}

The webhook workflow is as follows:

  1. It checks to see if the severity of the event is of interest; if not, it immediately returns a response without further processing.
  2. It then gathers data from the event: the event type, the URL and ZendPHP node triggering the event, the request ID, and the time it was triggered.
  3. It creates a Slack message that it sends to the #infra-php-issues channel with field attachments. In Slack, this message generates a table with the data.
  4. It sends the message.
  5. It returns a response indicating it has completed.

The data available in the webhook is detailed in the mon.get_issues method documentation of the Websocket API.