Catalog Webhook is a Backstage backend plugin that keeps another system in sync with your catalog. It runs on a schedule and sends only the updates your target needs.
The plugin watches for changes in your entities using ETag tracking. It batches payloads to control size. You can narrow what gets sent with an allow list or richer filters. It signs each request with HMAC SHA256 when a secret is set. It can fetch remote settings before each run, including a signal to reset its cache for a full resync. That way it avoids noisy retries and keeps traffic efficient.
Common use cases include mirroring your catalog into an internal system that powers scorecards or ownership views. You can also forward catalog data to tools that accept incoming webhooks. Atlassian Compass is one example that supports incoming webhooks for component events.
If you already run Backstage in your own stack, Catalog Webhook gives you a simple way to project clean catalog data into other places. It reduces duplicate scraping, cuts latency for downstream views, and helps you keep a single source of truth without manual exports.
Installation Instructions
These instructions apply to self-hosted Backstage only.
What this plugin does
This is a backend plugin. It runs on a schedule. It reads entities from your catalog. It sends only changed entities to a remote webhook. It uses ETags for change detection. It can sign payloads with HMAC SHA256 if you set a secret. It has no frontend. There are no UI components to add.
Install the backend package
Run this from the Backstage repo root.
yarn --cwd packages/backend add @forgedapps/backstage-catalog-webhook-plugin
Wire it into the new backend system
Edit packages/backend/src/index.ts. Add the module with backend.add. Keep your existing modules as they are.
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
// other modules you already use
backend.add(import('@backstage/plugin-catalog-backend'));
// add the catalog webhook module
backend.add(import('@forgedapps/backstage-catalog-webhook-plugin'));
backend.start();
The module registers on startup. It uses core services like scheduler and cache. It begins processing on its own.
Configure app config
Add this to app-config.yaml. Tweak values for your setup.
catalog:
webhook:
remoteEndpoint: 'https://your-remote-endpoint.com/webhook'
intervalMinutes: 10
secret: 'your-secret-key'
entityRequestSize: 500
entitySendSize: 50
allow:
- kind: ['api']
entityFilter:
- kind: ['Component', 'API']
- metadata.namespace: ['my-namespace']
Notes
- remoteEndpoint is required
- secret is optional but recommended
- allow is a strict kind list
- entityFilter is an OR filter set
Optional remote config support
The plugin will request remote config before each run. It calls your endpoint with a query of config. Put the response behind this path.
https://your-remote-endpoint.com/webhook?config
Return a JSON body like this. Both fields are optional.
{
"resetCache": true,
"entityFilter": [
{ "kind": ["Component"] },
{ "metadata.namespace": ["my-namespace"] }
]
}
See EntityFilterQuery docs https://backstage.io/docs/reference/catalog-client.entityfilterquery/
Webhook payload shape
The plugin sends a POST with this body.
{
"batchId": 1700000000000,
"entities": [],
"isFinalBatch": false
}
If you set a secret the request includes a signature header named X-Hub-Signature-256.
Validate signatures on your server
Use HMAC SHA256 with your shared secret. Compare against the X-Hub-Signature-256 header.
// node
const crypto = require('crypto');
function validateWebhook(req, secret) {
const signature = req.headers['x-hub-signature-256'];
if (!signature) {
throw new Error('No X-Hub-Signature-256 found on request');
}
const [algorithm, hash] = signature.split('=');
if (algorithm !== 'sha256') {
throw new Error('Unexpected hash algorithm');
}
const hmac = crypto.createHmac(algorithm, secret);
const digest = hmac.update(req.body).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(digest))) {
throw new Error('Request body digest did not match X-Hub-Signature-256');
}
}
How filtering works
- allow applies first and is strict
- entityFilter applies after allow
- remote filters from the config endpoint combine with your local filters
- allow remains the upper bound
Example
catalog:
webhook:
allow:
- kind: ['component', 'api', 'system']
entityFilter:
- kind: ['Component', 'Location']
- metadata.namespace: ['my-namespace']
Only the allowed kinds pass. Then the OR filters apply. Locations still drop due to allow.
Old backend system
This package is a backend module for the new backend system. It does not expose a legacy Express router. Use the new backend system to run it. Migrate your backend, then follow the steps above.
Changelog
This changelog is produced from commits made to the Catalog Webhook plugin since 8 months ago. It may not contain information about all commits. Releases and version bumps are intentionally omitted. This changelog is generated by AI.
3.3.3
Bug fixes
- Fix isFinalBatch logic to ensure proper server processing 16 days ago
Build
- Move yarn tsc to build script 16 days ago
Documentation
- Fix installation instruction in README 17 days ago
Release
- Release 3.3.3 16 days ago
Set up Backstage in minutes with Roadie
Focus on using Backstage, rather than building and maintaining it.
