Sentry Integration
Receive and route Sentry Integration Platform webhooks for issues, errors, alerts and comments.
Setup
1. Create a Source in Hookbase
curl -X POST https://api.hookbase.app/api/sources \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Sentry Production",
"slug": "sentry",
"provider": "sentry",
"signingSecret": "your-sentry-client-secret"
}'Save your webhook URL:
https://api.hookbase.app/ingest/{orgSlug}/sentry2. Create the Sentry Webhook
Webhooks come from an Integration Platform integration rather than from a project's alert settings, so the integration is what you configure:
- Create an integration for your Sentry organization, or open the one you already have
- Set its webhook URL to your Hookbase ingest URL
- Enable the resources you want delivered
- Copy the integration's client secret — this is the key the signature is computed with — and store
it on your Hookbase source as
signingSecret
See Sentry's webhook documentation for the resources an integration can subscribe to.
Info
One integration, one secret. The secret belongs to the integration, not to a project. If you split
production and staging across separate integrations, each one needs its own Hookbase source with its
own signingSecret; the two are not interchangeable.
3. Create Destinations and Routes
# Create a destination
curl -X POST https://api.hookbase.app/api/destinations \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{"name": "Incident Handler", "slug": "sentry-incidents", "url": "https://api.myapp.com/webhooks/sentry"}'
# Create a route
curl -X POST https://api.hookbase.app/api/routes \
-H "Authorization: Bearer whr_your_api_key" \
-H "Content-Type: application/json" \
-d '{"name": "Sentry to Incident Handler", "sourceId": "src_...", "destinationId": "dst_..."}'A route carries one destination. To send the same events to a second endpoint, create a second route.
Signature Verification
Sentry signs the raw request body with HMAC-SHA256 and sends the hex digest in the
Sentry-Hook-Signature header. There is no prefix and no key-value structure — the header value is
the signature:
Sentry-Hook-Signature: 5f1a9c0b2d3e4f60718293a4b5c6d7e8f9012a3b4c5d6e7f8091a2b3c4d5e6f7Hookbase verifies this automatically once the source has both fields set:
{
"provider": "sentry",
"signingSecret": "your-sentry-client-secret"
}The HMAC key is the secret exactly as you stored it: nothing is stripped from it and nothing is decoded, so leading or trailing whitespace pasted along with the value becomes part of the key and every signature fails.
There is no timestamp in this scheme, so nothing expires and no clock tolerance applies. A signed request captured today still verifies tomorrow, which is worth knowing if you replay traffic — the signature proves the body came from your integration, not that it is recent.
Once events are arriving with signature_valid: true, set rejectInvalidSignatures: true on the
source to have unverified requests refused with 401 instead of stored. It defaults to false, so a
wrong secret shows up as a bad badge rather than as dropped traffic until you turn it on.
Common Events
Sentry describes each delivery as a resource plus an action. The resource is selected on the
integration; the action is in the body as action.
| Resource | Action | Description |
|---|---|---|
issue | created | An issue was created |
issue | resolved | An issue was resolved |
issue | assigned | An issue was assigned |
issue | ignored | An issue was ignored |
error | created | An individual error event was recorded |
comment | created | A comment was added to an issue |
installation | created | Your integration was installed on an organization |
installation | deleted | Your integration was uninstalled |
Info
Expect events with no event type on this source. Hookbase derives an event type for a Sentry
source from the payload, trying event_type, then event, then type, then action. Sentry names
the resource in a header of its own rather than in the body, so the event type you see is normally the
action value (created, resolved, …) with no resource attached. Filter on payload fields when you
need to tell an issue webhook from a comment webhook.
Transform Examples
A javascript transform receives the parsed payload and returns the body Hookbase delivers.
Flatten an Issue Webhook
function transform(payload) {
const issue = (payload.data && payload.data.issue) || {};
return {
action: payload.action || null,
issue_id: issue.id || null,
title: issue.title || null,
culprit: issue.culprit || null,
permalink: issue.permalink || null,
installation: payload.installation ? payload.installation.uuid : null,
actor: payload.actor ? payload.actor.name : null,
received_at: new Date().toISOString()
};
}Slack Alert
function transform(payload) {
const issue = (payload.data && payload.data.issue) || {};
return {
text: `Sentry issue ${payload.action || "event"}: ${issue.title || issue.id || "unknown"}`,
blocks: [
{
type: "section",
fields: [
{ type: "mrkdwn", text: `*Action:*\n${payload.action || "unknown"}` },
{ type: "mrkdwn", text: `*Issue:*\n${issue.title || "unknown"}` },
{ type: "mrkdwn", text: `*Culprit:*\n${issue.culprit || "unknown"}` }
]
}
]
};
}Filter Examples
Filter conditions read dotted paths out of the payload. logic must be AND or OR.
Issue Webhooks Only
Because the resource is not in the body, select on the object the body carries instead.
{
"name": "Issue Webhooks",
"logic": "AND",
"conditions": [
{
"field": "data.issue",
"operator": "exists",
"value": ""
}
]
}New and Resolved Only
{
"name": "Created or Resolved",
"logic": "OR",
"conditions": [
{
"field": "action",
"operator": "equals",
"value": "created"
},
{
"field": "action",
"operator": "equals",
"value": "resolved"
}
]
}One Installation
{
"name": "Production Installation",
"logic": "AND",
"conditions": [
{
"field": "installation.uuid",
"operator": "equals",
"value": "your-installation-uuid"
}
]
}Headers
| Header | Description |
|---|---|
Sentry-Hook-Signature | Hex HMAC-SHA256 of the raw body, no prefix |
Sentry sends further headers of its own naming the resource and the delivery. Hookbase uses
Sentry-Hook-Signature for verification and does not store or replay the rest: a delivery to an HTTP
destination carries Hookbase's own X-Event-ID and X-Delivery-ID headers plus whatever the
destination is configured to add. Anything your handler needs should come out of the payload.
Troubleshooting
Signature Verification Failed
- Confirm the source's
providerissentry— a source left oncustomreadsX-Signature,X-Webhook-SignatureandX-Hub-Signature-256, and Sentry sendsSentry-Hook-Signature. A source with a secret but no provider at all verifies as if it werecustom, which fails the same way - Confirm
signingSecretis the integration's client secret, with no surrounding whitespace. The secret is used as its own bytes, so anything extra changes the key - Confirm the secret belongs to the integration that is delivering, not to another one in the same organization
- Check nothing between Sentry and Hookbase rewrites the body. The digest covers the raw bytes, so a proxy that reformats JSON invalidates a genuine signature
No Events Arriving
- Check the integration's webhook URL matches your ingest URL exactly, org and source slugs included
- Check the resources are enabled on the integration — an integration with none selected sends nothing
- Check the integration is installed on the organization whose events you expect
Duplicate Events
Hookbase has no provider event-id extractor for Sentry, so on a source with dedupEnabled: true the
default auto strategy falls back to hashing the payload: two byte-identical deliveries inside the
dedup window collapse into one, and two deliveries that differ in any field do not. If you put a proxy
in front that adds an idempotency header, name it in dedupCustomHeader to dedup on that instead.
Separately from that, a source with a signing secret gets a 24-hour payload-hash replay check on every
request whose signature verified, whether or not dedupEnabled is on.