Guide to Confluence Data Center Webhooks: Features and Best Practices
Confluence Data Center webhooks tell other systems when something happens in your wiki: a page is published, edited, moved or deleted, a comment is added, a space is created, a label is applied, or a user account is deactivated. Teams use them to update issue trackers, post to chat, or keep search indexes and audit logs in step with Confluence.
This guide covers the webhooks built into Confluence Data Center, the self-managed edition: how to create them, how they are signed, the events and their ID-only payloads, Confluence's circuit breaker, and the best practices for running a receiver in production. Confluence Cloud works differently, through app webhooks with their own authentication, and is not covered here.
What are Confluence Data Center webhooks?
A Confluence Data Center webhook has a title, a URL, a set of events and an optional secret. An administrator creates it in the admin console or through the REST API, and when a selected event happens, Confluence sends a JSON payload to the URL. Webhooks are available from Confluence 7.7.
Payloads are deliberately thin. They contain mostly IDs, so that identifiable information isn't stored by third-party services or shown to people who aren't allowed to see it. Your receiver uses the Confluence REST API to look up the details.
If you set a secret, Confluence signs each request with an HMAC of the body, keyed with the secret, and puts it in an X-Hub-Signature header. HMAC-SHA256 is the default. If you don't set a secret, requests aren't signed.
Confluence webhook features
| Feature | Details |
|---|---|
| Availability | Confluence Data Center and Server 7.7 or later |
| Configuration | Administration > General Configuration > Webhooks, or POST <CONFLUENCE_ |
| Permissions | Confluence Administrator or System Administrator |
| Format | JSON, mostly IDs |
| Event name | event in the body |
| Signature header | X-Hub-Signature, only when a secret is set |
| Scheme | HMAC-SHA256 by default. sha256=<hex> in Atlassian's sibling Bitbucket docs |
| Signed content | The received body |
| Secret | Defined by you, up to 255 characters |
| Replay window | None. No timestamp is signed |
| Testing | Test connection on the create form |
| Failure handling | Circuit breaking: skipped after five failures, from 10 seconds up to 10 hours |
| Concurrency limit | Webhooks are skipped while 500 are in flight |
Because Confluence Data Center is self-managed, requests come from your own Confluence nodes, and there's no Atlassian IP list to allowlist. Your endpoint must be reachable from those nodes, which often sit behind a corporate firewall.
Common events
Confluence documents 43 events. The names for deleted content are easy to misread: _removed means sent to the trash, and _trashed means purged from the trash.
| Area | Events |
|---|---|
| Pages | page_created, page_updated, page_moved, page_removed, page_restored, page_trashed, page_children_reordered, blueprint_page_created |
| Blog posts | blog_created, blog_updated, blog_removed, blog_restored, blog_trashed |
| Attachments | attachment_created, attachment_updated, attachment_removed, attachment_restored, attachment_trashed |
| Comments | comment_created, comment_updated, comment_removed |
| Any content | content_created, content_updated, content_restored, content_trashed, content_ |
| Labels | label_added, label_removed, label_created, label_deleted |
| Spaces | space_created, space_updated, space_removed, space_logo_updated, space_, theme_enabled |
| Users and groups | user_created, user_deactivated, user_reactivated, user_removed, user_followed, group_created, group_removed |
Some events have narrow triggers. page_updated fires when unpublished changes are published, not on every keystroke. page_children_reordered fires only when the page order is switched to alphabetical in Space Tools > Reorder pages, not when you drag pages. space_ doesn't fire for changes made with Inspect Permissions.
The generic content_* events overlap with the specific ones. Publishing a page can trigger both page_created and content_created if you subscribe to both.
Here is Confluence's page_trashed example, the only payload it publishes:
{
"timestamp": 1596182511300,
"event": "page_trashed",
"userKey": "ff80818154ec9913015501e194f601d8",
"page": { "id": 309264476 }
}
timestamp is in milliseconds, userKey identifies the user who caused the event, and the affected object is an object of IDs. Confluence doesn't document the object key or fields for other events, so log a few real payloads before you write parsers for them.
Setting up Confluence Data Center webhooks
Set a secret. Hookdeck's Confluence Data Center source verifies X-Hub-Signature, and without a secret Confluence sends no signature, so every request fails verification.
- Go to Administration > General Configuration > Webhooks.
- Enter a title and your endpoint URL.
- Enter a secret of up to 255 characters and store the same value server-side, for example as
CONFLUENCE_.WEBHOOK_ SECRET - Select Test connection to check that Confluence can reach the endpoint.
- Choose the events, select Active, and click Create.
Or create the webhook through the REST API:
curl -X POST "$CONFLUENCE_URL/rest/api/webhooks" \
-u "$CONFLUENCE_USER:$CONFLUENCE_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"name": "Page events",
"events": ["page_created", "page_updated", "page_removed"],
"configuration": { "secret": "a-long-random-secret" },
"url": "https://example.com/webhooks/confluence",
"active": true
}'
If you use Hookdeck, create a Confluence Data Center source, set the same secret on it, and use the Hookdeck source URL as the webhook URL. Your Confluence nodes need outbound access to it. If you choose to run a webhook without a secret, leave verification off on the source.
Securing Confluence Data Center webhooks
Compute an HMAC-SHA256 of the raw request body with your secret, encode it as hex, and compare it with the value in X-Hub-Signature in constant time.
Confluence's own page says only that the header "contains the HMAC". Atlassian's Bitbucket Data Center page, which uses the same wording and shows an example, gives the value as sha256=<hex>. Accept the digest with or without the sha256= prefix, and capture a real delivery to confirm.
const crypto = require('crypto');
const express = require('express');
function verifyConfluenceSignature(rawBody, signatureHeader, secret) {
if (!signatureHeader) return false;
let received = signatureHeader.trim().toLowerCase();
if (received.startsWith('sha256=')) received = received.slice('sha256='.length);
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(received, 'utf8');
const b = Buffer.from(expected, 'utf8');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
const app = express();
app.post('/webhooks/confluence', express.raw({ type: '*/*' }), (req, res) => {
const secret = process.env.CONFLUENCE_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500); // fail closed
if (!verifyConfluenceSignature(req.body, req.get('x-hub-signature'), secret)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString('utf8'));
// Route on event.event, then look up the IDs through the REST API asynchronously.
res.sendStatus(200);
});
The same check in Python:
import hashlib
import hmac
def verify_confluence_signature(raw_body: bytes, signature_header, secret: str) -> bool:
if not signature_header:
return False
received = signature_header.strip().lower()
if received.startswith("sha256="):
received = received[len("sha256="):]
expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(received.encode("utf-8"), expected.encode("utf-8"))
# In FastAPI: raw_body = await request.body()
# verify_confluence_signature(raw_body, request.headers.get("x-hub-signature"),
# os.environ["CONFLUENCE_WEBHOOK_SECRET"])
To check your verifier, use the secret confluence-test-secret and this exact body:
{"timestamp":1596182511300,"event":"page_trashed","userKey":"ff80818154ec9913015501e194f601d8","page":{"id":309264476}}
The header should be:
sha256=d48eb86ce370d785a67df14c214df84c4520b96b0aed57ccdf4c117aff7dd299
Make Confluence webhooks production-ready. Hookdeck Event Gateway verifies the
X-Hub-Signatureheader, accepts every page, comment and space event as soon as it arrives, and durably queues it, so your receiver's failures never trip Confluence's circuit breaker.
Confluence webhook limitations and pain points
Data Center only
The Problem: You can't find the webhook settings, or the events and headers don't match this guide.
Why It Happens: These admin webhooks belong to Confluence Data Center and Server. Confluence Cloud has no General Configuration > Webhooks page. Its webhooks are part of Connect and Forge apps, with different event names and authentication.
Workarounds:
- Check which edition you run before you build anything.
- On Cloud, follow Atlassian's app webhook documentation instead.
How Hookdeck Can Help: Hookdeck's Confluence Data Center source is built for these admin webhooks. For other sources, Hookdeck's generic webhook source still gives you queuing, retries and replay.
Failures trip a circuit breaker
The Problem: After a few errors, events stop arriving for up to 10 hours, and events triggered in that time may never reach you.
Why It Happens: Confluence counts a webhook as unhealthy after five failures and skips it, first for 10 seconds, then for longer and longer periods, up to 10 hours. Confluence doesn't document retries for an individual delivery, or say whether a skipped event is sent later. Webhooks are also skipped while 500 webhook requests are in flight.
Workarounds:
- Keep the receiver fast and highly available, so it doesn't rack up failures.
- After an outage, reconcile through the Confluence REST API, for example by searching for content changed in that window.
How Hookdeck Can Help: Hookdeck accepts each request as soon as it arrives and stores it, so your service's failures don't count against Confluence's circuit breaker. Hookdeck then retries delivery to your service on a schedule you control.
Payloads are mostly IDs
The Problem: A page_updated event doesn't tell you the page's title, space or content.
Why It Happens: Confluence sends IDs on purpose, to keep identifiable information out of third-party systems.
Workarounds:
- Look up each ID through the Confluence REST API with a service account that has the access you need.
- Do the lookups asynchronously, after acknowledging the webhook.
- Cache lookups for busy pages and spaces.
How Hookdeck Can Help: Hookdeck lets you acknowledge Confluence immediately and deliver to a worker that does the lookups at its own pace, with rate limits that protect your Confluence instance's API.
The signature format comes from another product's docs
The Problem: Verification fails, and Confluence's own page doesn't show what the header looks like.
Why It Happens: The Confluence page names the header and the default algorithm, but not the value format. The sha256=<hex> form is documented on the Bitbucket Data Center page, which shares the wording.
Workarounds:
- Accept the hex digest with or without
sha256=, as in the code above. - Log the header of one real delivery and check it against the test vector.
How Hookdeck Can Help: Hookdeck's Confluence Data Center source accepts both forms, so you don't need to settle the question yourself.
No event id or replay protection
The Problem: You can't tell a repeated request from a new event, and a captured request still verifies.
Why It Happens: The payload has a timestamp but no event id, and the signature covers only the body.
Workarounds:
- Build a key from
event, the object ID andtimestamp. - Keep the endpoint on HTTPS.
How Hookdeck Can Help: Hookdeck's deduplication can drop repeated deliveries based on the fields you choose.
Best practices
Verify against the raw body
The signature covers the exact bytes Confluence sent. In Express, mount express.raw() on the webhook route and parse after verification. In a Next.js route handler, read await request.text(). In FastAPI, read await request.body().
Pick specific or generic events, not both
Subscribe to page_* and blog_* events, or to content_* events, but avoid both for the same purpose, or you'll process each change twice.
Read _removed and _trashed carefully
page_removed means a page went to the trash and can still be restored. page_trashed means it was purged for good. Map them to "soft delete" and "hard delete" in your system.
Acknowledge fast and process asynchronously
Return 200 once the request is verified, then fetch details from the REST API. See why to process webhooks asynchronously and our guide to webhook idempotency.
Conclusion
Confluence Data Center signs webhooks only when they have a secret, with an HMAC-SHA256 of the body in X-Hub-Signature. Always set a secret, accept the digest with or without sha256=, treat payloads as IDs to look up, keep _removed and _trashed straight, and keep your receiver healthy, because repeated failures make Confluence skip the webhook for up to 10 hours.
Hookdeck Event Gateway sits in front of your receiver, checks every X-Hub-Signature, and holds each event until your service has processed it.
Get started with Hookdeck for free and handle Confluence webhooks reliably in minutes.