Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,13 @@ def refresh_token(install_id):

## Verifying Installations

Typically, if you have the redirect URL configured, there is work happening on your end to "finalize" the installation. If this is the case, we recommend enabling the "Verify Install" option for your integration. Once enabled, you'll need to send a request marking the installation as officially "installed":
Typically, if you have the redirect URL configured, there is work happening on your end to "finalize" the installation. If this is the case, we recommend enabling the "Verify Install" option for your integration. Once enabled, you'll need to send a request marking the installation as officially "installed". Authenticate the request with the token you received for this installation during the [OAuth process](#oauth-process):

```python
requests.put(
u'https://sentry.io/api/0/sentry-app-installations/{}/'.format(install_id),
json={'status': 'installed'},
headers={'Authorization': f'Bearer {token}'},
)
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ The UI components are specified in the schema section of the integration details
The `uri` keys in your schema elements describe the URI path that will receive a request from that UI component. Sentry will append the path in the `uri` field onto the host of your integration's _webhook URL_.
For example, if the webhook URL is `https://example.com/webhook/`, and your schema's `uri` attribute is `/alerts/`, then the integration will receive requests to `https://example.com/alerts/`.

## Verifying Requests

Sentry signs requests from UI components with your _Client Secret_, the same way it signs [webhooks](/integrations/integration-platform/webhooks/#sentry-hook-signature). The only difference is the header name: UI component requests carry the signature in the `Sentry-App-Signature` header, not `Sentry-Hook-Signature`.

Verify the signature against the raw request body, using the same code as for webhooks. Keep these details in mind:

- [Select](/integrations/integration-platform/ui-components/formfield/#select) option requests are `GET` requests with no body, so Sentry computes the signature over an empty string. These requests still include a `Content-Type: application/json` header, so make sure your framework doesn't reject or rewrite the empty body before you verify it.
- The signature covers only the body. The query parameters of select option requests, such as `installationId`, aren't signed.

## Error Handling

Component rendering either 100% works or shows nothing. To protect the integration from looking chaotic due to errors we have no control over, if any part of the third-party component rendering fails, nothing will render.
Expand Down
47 changes: 30 additions & 17 deletions docs/integrations/integration-platform/webhooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,40 +55,53 @@ This header represents a cryptographic hash generated by your _Client Secret_. I

**Verifying the Signature**

The below code snippet lets you validate the signature with the event payload.
Sentry computes the signature as an HMAC-SHA256 hex digest of the exact request body bytes, using your _Client Secret_ as the key. Compute the digest over the raw body as received. Don't parse the JSON and serialize it again, because the result won't always match the bytes Sentry signed.

```javascript
const crypto = require("crypto");

function verifySignature(request, secret = "") {
const hmac = crypto.createHmac("sha256", secret);
hmac.update(JSON.stringify(request.body), "utf8");
const digest = hmac.digest("hex");
return digest === request.headers["sentry-hook-signature"];
// rawBody must be the unparsed request body, for example from
// express.raw({ type: "application/json" })
function verifySignature(rawBody, signature, secret) {
if (!signature) {
return false;
}
const digest = Buffer.from(
crypto.createHmac("sha256", secret).update(rawBody).digest("hex")
);
const expected = Buffer.from(signature);
return (
digest.length === expected.length &&
crypto.timingSafeEqual(digest, expected)
);
}

verifySignature(request.body, request.headers["sentry-hook-signature"], secret);
```

```python
import hashlib
import hmac
import json

expected_digest = request.headers.get('sentry-hook-signature') # returns None if header is missing
body = json.dumps(request.body)

digest = hmac.new(
key=client_secret.encode('utf-8'),
msg=body,
digestmod=hashlib.sha256,
).hexdigest()
def verify_signature(raw_body: bytes, signature: str | None, client_secret: str) -> bool:
if not signature: # The signature is missing
return False
digest = hmac.new(
key=client_secret.encode("utf-8"),
msg=raw_body,
digestmod=hashlib.sha256,
).hexdigest()
return hmac.compare_digest(digest, signature)

if not expected_digest: # The signature is missing
raise UnauthorizedError

if not hmac.compare_digest(digest, expected_digest):
# For example, in Flask:
if not verify_signature(request.get_data(), request.headers.get("sentry-hook-signature"), client_secret):
raise UnauthorizedError
```

Requests from [UI components](/integrations/integration-platform/ui-components/#verifying-requests) are signed the same way, but use the `Sentry-App-Signature` header instead.

## Request Structure

All webhook requests have some common elements.
Expand Down
Loading