Generic plugin for OJS 3.5 (plugin folder: webhook) that sends an HTTP
webhook with the article's metadata to a configurable URL whenever an
article is published for the first time.
-
The plugin registers a listener on the core
PublicationPublishedevent. -
Whenever a publication is saved, it checks whether this is the transition from not published to published (
oldPublication.status !== PUBLISHED && publication.status === PUBLISHED). Re-saving an already-published article does not trigger another webhook. -
If a valid
webhookUrlis configured, aSendWebhookJobis queued on the OJS job queue. The actual HTTP request therefore runs asynchronously and does not block the "Publish" action in the backend. -
The job loads the submission, publication, issue, section and journal and sends the following JSON payload via
POST:Field Source authorPublication::getShortAuthorString()article_titlePublication::getLocalizedTitle()published_dateIssue::getDatePublished()(Y-m-d)issue_titleIssue::getLocalizedTitle()abstractPublication::getLocalizedData('abstract')rich_pagesSection::getLocalizedTitle()keywordsPublication::getLocalizedData('keywords')legalPublication::getData('rights')published_instituteJournal::getData('publisherInstitution')issue_volumeIssue::getVolume()issue_numberIssue::getNumber()issue_yearIssue::getYear()If a
webhookSecretis configured, it is sent unchanged in theX-Webhook-Secretheader so the receiving endpoint can attribute the request to this journal.
Accessible from the plugin grid (Website > Plugins > Generic Plugins) via
the gear icon, regardless of whether the plugin is currently enabled.
Settings are stored per journal (context).
| Field | Required | Description |
|---|---|---|
| Webhook URL | no* | Target URL for the POST request. http://localhost:<port>/... is accepted for local development. May be left empty to reset the configuration (see below). |
| Webhook Secret | no | Sent as the X-Webhook-Secret header. |
* The field can be saved empty (e.g. to clear a previously configured URL), but a syntactically valid URL must be present before the plugin can be enabled.
Saving the form reloads the page so the plugin grid immediately reflects the updated configuration (see below).
- Freshly installed, the plugin is disabled (
settings.xml). - It can only be enabled once a syntactically valid
webhookUrlhas been saved (WebhookPlugin::getCanEnable()). As long as no valid URL is configured, the "Enable" toggle in the plugin grid is greyed out and the plugin description shows a corresponding hint. - Order of operations: open the gear icon → enter and save the webhook URL → enable the plugin in the grid.
- Clearing the webhook URL again (saving an empty value) resets the configuration. It does not automatically disable an already-enabled plugin, but the "Enable" toggle becomes unavailable again until a valid URL is configured.
- OJS 3.5.
- A running job queue worker (e.g.
php tools/jobs.php processor the scheduler set up in OJS 3.5), since the webhook is sent asynchronously via the queue. Without a running worker, jobs stay queued and are never sent.
- No SSRF protection: the target URL is not checked against private or
internal addresses. This is intentional for local development
(
localhost,127.0.0.1are accepted on purpose), but it widens the attack surface in production if a journal manager can enter arbitrary URLs. - No HMAC signing:
webhookSecretis sent as a plain-text header rather than being used to sign the payload, so it offers no protection against payload tampering. - No retry/backoff configuration:
SendWebhookJobrelies onBaseJob's defaults. If the endpoint is permanently unreachable, there is no notification to the journal manager, only an entry in the job/failure logs.
WebhookUrlValidator (the http(s)-only URL check shared by WebhookPlugin's
enable-gate and WebhookSettingsForm's field validator) has no OJS/PKP
dependency and is covered by a PHPUnit suite that runs standalone, without an
OJS installation:
composer install
vendor/bin/phpunit
The rest of the plugin (event registration, the settings form, the queue job) depends on PKP/Laravel classes that only exist inside a running OJS instance and is not covered by this suite.
webhook/
├── WebhookPlugin.php Plugin class, event listener, enable gate
├── WebhookSettingsForm.php Settings form (webhook URL/secret)
├── WebhookUrlValidator.php Shared http(s) URL validation
├── SendWebhookJob.php Async queue job for the HTTP POST
├── settings.xml Install defaults (enabled = false)
├── version.xml Plugin version information
├── templates/settings.tpl Smarty template for the settings form
├── locale/{de,en}/locale.po Translations
└── tests/ PHPUnit tests (WebhookUrlValidator only, see Development)