Webhooks "send" based on book or shelf #4774

Open
opened 2026-02-05 09:14:50 +03:00 by OVERLORD · 1 comment
Owner

Originally created by @tritanium73 on GitHub (May 14, 2024).

Describe the feature you'd like

It would be great if you could send webhooks based on the source:

Book 1 -> webhook target a
Book 2 -> webhook target b

or

Shelf x

  • Book 3
  • Book 4

to webhook target c

Describe the benefits this would bring to existing BookStack users

This would allow us to notify our MS Teams channels in a more granular manner so that only certain books go to certain MS Teams channels

Can the goal of this request already be achieved via other means?

Not really, because when I create the webhook I unfortunately can't specify a source that the webhook end should refer to

Have you searched for an existing open/closed issue?

  • I have searched for existing issues and none cover my fundamental request

How long have you been using BookStack?

3 months to 1 year

Additional context

No response

Originally created by @tritanium73 on GitHub (May 14, 2024). ### Describe the feature you'd like It would be great if you could send webhooks based on the source: Book 1 -> webhook target a Book 2 -> webhook target b or Shelf x - Book 3 - Book 4 to webhook target c ### Describe the benefits this would bring to existing BookStack users This would allow us to notify our MS Teams channels in a more granular manner so that only certain books go to certain MS Teams channels ### Can the goal of this request already be achieved via other means? Not really, because when I create the webhook I unfortunately can't specify a source that the webhook end should refer to ### Have you searched for an existing open/closed issue? - [X] I have searched for existing issues and none cover my fundamental request ### How long have you been using BookStack? 3 months to 1 year ### Additional context _No response_
OVERLORD added the 🔨 Feature Request label 2026-02-05 09:14:50 +03:00
Author
Owner

@DanielGordonIT commented on GitHub (May 21, 2024):

I'd suggest the theme system, but I don't think the webhook event covers it:

public function __construct(Webhook $webhook, string $event, Loggable|string $detail)
{
    $this->webhook = $webhook;
    $this->initiator = user();
    $this->initiatedTime = time();

    $themeResponse = Theme::dispatch(ThemeEvents::WEBHOOK_CALL_BEFORE, $event, $this->webhook, $detail, $this->initiator, $this->initiatedTime);
    $this->webhookData =  $themeResponse ?? WebhookFormatter::getDefault($event, $this->webhook, $detail, $this->initiator, $this->initiatedTime)->format();
}

As far as I can tell, it can't change the webhook's data, only change the data that gets sent. It would be weird if you could change endpoints in place. The only other option would be to make a different webhook for each channel you want, and then set the output to empty if the event is for a book you don't care about for that endpoint:

<?php

use BookStack\Activity\Models\Loggable;
use BookStack\Activity\Models\Webhook;
use BookStack\Activity\Tools\WebhookFormatter;
use BookStack\Facades\Theme;
use BookStack\Theming\ThemeEvents;
use BookStack\Users\Models\User;

function selectiveFormat(array $defaultWebhookData): array
{
    $book_id = $defaultWebhookData['related_item']['book_id'];
    $webhook_name = $defaultWebhookData['webhook_name']
    if ( !(
        ($book_id == 1 && $webhook_name == "Team 1 Webhook") ||
        ($book_id == 2 && $webhook_name == "Team 2 Webhook") ||
        ($book_id == 3 && $webhook_name == "Team 3 Webhook") ||
        ($book_id == 4 && $webhook_name == "Team 4 Webhook")
    )
    ) {
        // If it's a teams webhook, but the name and book ID don't match, then just return nothing and cause a 400 error.
        return [];
    }
    return null;

}

Theme::listen(ThemeEvents::WEBHOOK_CALL_BEFORE, function (
    string $event,
    Webhook $webhook,
    string|Loggable $detail,
    User $initiator,
    int $initTime,
) {
    if (str_starts_with($webhook->endpoint, 'https://teams.webhook.whatever')) {
        $defaultData = WebhookFormatter::getDefault($event, $webhook, $detail, $initiator, $initTime);
        return selectiveFormat($defaultData->format());
    }

    // Otherwise return null to leave the webhook data alone
    return null;
});

This will return [] when a book ID and webhook name according to your manual specifications don't match, but will return the normal value when they do.

The main problem with this is that it scales pretty badly with each team added - if you have 5 teams, that's 5 events firing and 5 webhooks trying to send data per action the webhook is listening to.

This is pretty heavily adapted from this page where Dan Brown went over formatting webhooks differently for Pushover.

(Disclaimer: I haven't tested this code due to time constraints on my end, so there might be some issues with how it grabs the data from the webhook for the webhook name and book ID, but that should be easy enough to debug)

@DanielGordonIT commented on GitHub (May 21, 2024): I'd suggest the theme system, but I don't think the [webhook event](https://github.com/BookStackApp/BookStack/blob/f937bf3abb44db21fbada3180e66bfa27dc3f7da/app/Activity/DispatchWebhookJob.php#L37) covers it: ```php public function __construct(Webhook $webhook, string $event, Loggable|string $detail) { $this->webhook = $webhook; $this->initiator = user(); $this->initiatedTime = time(); $themeResponse = Theme::dispatch(ThemeEvents::WEBHOOK_CALL_BEFORE, $event, $this->webhook, $detail, $this->initiator, $this->initiatedTime); $this->webhookData = $themeResponse ?? WebhookFormatter::getDefault($event, $this->webhook, $detail, $this->initiator, $this->initiatedTime)->format(); } ``` As far as I can tell, it can't change the webhook's data, only change the data that gets sent. It _would_ be weird if you could change endpoints in place. The only other option would be to make a different webhook for each channel you want, and then set the output to empty if the event is for a book you don't care about for that endpoint: ```php <?php use BookStack\Activity\Models\Loggable; use BookStack\Activity\Models\Webhook; use BookStack\Activity\Tools\WebhookFormatter; use BookStack\Facades\Theme; use BookStack\Theming\ThemeEvents; use BookStack\Users\Models\User; function selectiveFormat(array $defaultWebhookData): array { $book_id = $defaultWebhookData['related_item']['book_id']; $webhook_name = $defaultWebhookData['webhook_name'] if ( !( ($book_id == 1 && $webhook_name == "Team 1 Webhook") || ($book_id == 2 && $webhook_name == "Team 2 Webhook") || ($book_id == 3 && $webhook_name == "Team 3 Webhook") || ($book_id == 4 && $webhook_name == "Team 4 Webhook") ) ) { // If it's a teams webhook, but the name and book ID don't match, then just return nothing and cause a 400 error. return []; } return null; } Theme::listen(ThemeEvents::WEBHOOK_CALL_BEFORE, function ( string $event, Webhook $webhook, string|Loggable $detail, User $initiator, int $initTime, ) { if (str_starts_with($webhook->endpoint, 'https://teams.webhook.whatever')) { $defaultData = WebhookFormatter::getDefault($event, $webhook, $detail, $initiator, $initTime); return selectiveFormat($defaultData->format()); } // Otherwise return null to leave the webhook data alone return null; }); ``` This will return `[]` when a book ID and webhook name according to your manual specifications don't match, but will return the normal value when they do. The main problem with this is that it scales pretty badly with each team added - if you have 5 teams, that's 5 events firing and 5 webhooks trying to send data per action the webhook is listening to. This is pretty heavily adapted from [this page](https://www.bookstackapp.com/hacks/pushover-webhooks/) where Dan Brown went over formatting webhooks differently for Pushover. (Disclaimer: I haven't tested this code due to time constraints on my end, so there might be some issues with how it grabs the data from the webhook for the webhook name and book ID, but that should be easy enough to debug)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/BookStack#4774