Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1040 Update WH docs #2284

Merged
merged 3 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/_snippets/webhook-signature-validation.mdx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
```typescript

const webhookKey = "your_secret_key"; // Webhook key from the Settings page
const signature = req.headers["x-metriport-signature"];
const signatureAsString = // codeToValidateAndConvertToString(signature);
const metriportApi = new MetriportMedicalApi(apiKey, {
baseAddress: apiUrl,
});
if (metriportApi.verifyWebhookSignature(whKey, req.body, signatureAsString)) {
if (metriportApi.verifyWebhookSignature(webhookKey, req.body, signatureAsString)) {
console.log(`Signature verified`);
} else {
console.log(`Signature verification failed`);
Expand Down
57 changes: 28 additions & 29 deletions docs/home/api-info/webhooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,39 @@ To enable this integration approach with Metriport:
1. Set this endpoint URL on the [Developers page](https://dash.metriport.com/developers) in the developer dashboard,
or via the [Update Settings endpoint](/home/api-reference/settings/post-settings);
1. This will generate a webhook key that should be used to authenticate requests on
your app's endpoint (webhook).
your app's endpoint (webhook) - see [authentication](#authentication) and
[generating a new webhook key](#generating-a-new-webhook-key) below.

General requirements for the endpoint:

1. Public endpoint accessible from the internet;
1. Does not do an HTTP redirect (redirects will not be followed);
1. Accepts a `POST` HTTP request;
1. Verifies requests by comparing the HTTP header `x-webhook-key` with the webhook key;
1. Verifies requests using the HTTP header `x-metriport-signature` - see [authentication](#authentication) below;
1. Accepts and responds to a [`ping` message](#the-ping-message);
1. Be [idempotent](https://en.wikipedia.org/wiki/Idempotence) - it should accept being called more
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some thoughts/Qs:

  • Is the "idempotent" requirement essentially just telling the cx that we always expect a 200 HTTP response? Perhaps we should just tell them that, since we don't really care what they do on their end?
  • We've had problems where cx webhook endpoints take a long time to process the payload, and we timeout waiting for the response. Should we instruct them to process the payload async and return us 200 right away?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the "idempotent" requirement essentially just telling the cx that we always expect a 200 HTTP response? Perhaps we should just tell them that, since we don't really care what they do on their end?

No, saying it should be idempotent is not the same thing as saying it should always return 200. They need to design their endpoint in a way that allows it being called multiple times w/ the same payload w/o breaking their app.

We've had problems where cx webhook endpoints take a long time to process the payload, and we timeout waiting for the response. Should we instruct them to process the payload async and return us 200 right away?

Good point, added!

than once with the same payload, with no changes to the end result.

Additionally, depending on what Metriport APIs you're using, your endpoint will need to accept and process
different messages when they become available - specifically:
Additionally, your endpoint will need to accept and process different messages when they become available - specifically:

1. If using the Devices API - accept and process [user data messages](#devices-api-messages).
1. If using the Medical API - accept and process [patient data messages](#medical-api-messages).
1. [Patient data messages](#medical-api-messages).

## Authentication

When Metriport sends a webhook message, it includes an `x-metriport-signature` header.
When Metriport sends a webhook message, it includes an `x-metriport-signature` header.

- `x-metriport-signature`: This is an [HMAC](https://en.wikipedia.org/wiki/HMAC) SHA-256 hash computed using your webhook key and the body of the webhook message.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: formatting here is a bit weird - suggestion to combine the two:

When Metriport sends a webhook message, it includes an x-metriport-signature header - this is an HMAC SHA-256 hash computed using your webhook key and the body of the webhook message.


At a high level, an HMAC works by taking a secret key and a message, and performing iterative hashes of the two to create
a signature. That signature is compared against the signature in the header for equality. If the signatures are equal, you can trust the webhook
payload is authentic and has not been tampered with. If they aren't equal, you should throw it away.
At a high level, an HMAC works by taking a secret key (webhook key from the Settings page) and a message,
and performing iterative hashes of the two to create a signature. That signature is compared against the
signature in the header for equality. If the signatures are equal, you can trust the webhook
payload is authentic and has not been tampered with. If they aren't equal, you should throw it away.

You can use this header to verify that the webhook messages sent to your endpoint are from Metriport. Here's an example of how you can do this in Node.js:

<Snippet file="webhook-signature-validation.mdx" />

The important thing is to make sure you use a trusted cryptography library in whatever language you choose to validate the webhook message in.
The important thing is to make sure you use a trusted cryptography library in whatever language you choose to validate the webhook message in.
You can also compute an HMAC in python:

```python
Expand All @@ -63,7 +65,7 @@ def compute_hmac(key, message, signature, digestmod=hashlib.sha256):
"""
Verify the HMAC signature for a given message and key.

:param key: The secret key (string).
:param key: The webhook key from the Settings page (string).
:param message: The message to be authenticated (string in JSON format).
:param signature: The provided HMAC signature to verify against (string).
:param digestmod: The hash function to use (defaults to hashlib.sha256).
Expand All @@ -77,9 +79,9 @@ def compute_hmac(key, message, signature, digestmod=hashlib.sha256):
return signature == computed_signature

# Example usage
key = 'your_secret_key' # Your key
key = 'your_secret_key' # The webhook key from the Settings page
message = '{"webhookUrl": "https://api.app.com/webhook"}' # The message from the request in JSON format
signature = 'the-request-signature' # The signature from the header
signature = 'the-request-signature' # The signature from the header

is_signature_valid = compute_hmac(key, message, signature)

Expand All @@ -92,16 +94,18 @@ else:
```

<Warning>
The previous method of authenticating webhooks, comparing your webhook key with the `x-webhook-key` in each request's HTTP header, is being deprecated on **December 9th, 2023**.
Please update your implementation to use the `x-metriport-signature` header for authentication.
The previous method of authenticating webhooks, comparing your webhook key with the
`x-webhook-key` in each request's HTTP header, is being deprecated on **December 9th, 2023**.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's update the date, or take this out?

we're a bit past December 9th, 2023 😛

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed that, removed!

Please update your implementation to use the `x-metriport-signature` header for authentication.
</Warning>

### Generating a new webhook key

If using the dashboard: simply delete your webhook URL on the [Developers page](https://dash.metriport.com/developers), save, and enter it again.
If using the dashboard: simply delete your webhook URL on the [Developers page](https://dash.metriport.com/developers),
save, and enter it again.

If using the API: set the webhook URL to an empty string via the [Update Settings endpoint](/home/api-reference/settings/post-settings), and then set it
to your desired URL making another request to the same endpoint.
If using the API: set the webhook URL to an empty string via the [Update Settings endpoint](/home/api-reference/settings/post-settings),
and then set it to your desired URL making another request to the same endpoint.

## Format

Expand All @@ -110,7 +114,6 @@ Webhook requests contain the relevant information on the body of the HTTP reques
There are several types of messages you can expect to receive:

- [`ping`](#the-ping-message): validation of the webhook connection between Metriport and your app;
- [Devices API messages](#devices-api-messages);
- [Medical API messages](#medical-api-messages).

In general, upon successful receiving of these messages, it's expected that your app responds with a `200` HTTP
Expand Down Expand Up @@ -145,12 +148,6 @@ accept a `POST` request with this body...
You can check the [webhook mock server available on our repository](https://github.com/metriport/metriport/blob/master/utils/src/mock-webhook.ts)
for a simple implementation of this message.

### Devices API messages

When using the Devices API, Metriport will send Webhook messages containing [new provider connections](/devices-api/more-info/webhooks#provider-connected-message) for each user, as well as [user data](/devices-api/more-info/webhooks#user-health-data-message) to your app from [our supported Providers](/devices-api/more-info/our-integrations), as soon as the data becomes available.

You can see Webhook details specific to the Devices API on [this page](/devices-api/more-info/webhooks).

### Medical API messages

When using the Medical API, Metriport will send Webhook messages containing status updates to your app, as soon
Expand All @@ -170,8 +167,9 @@ Example payload:
"meta": {
"messageId": "<message-id>",
"when": "<date-time-in-utc>",
"type": "devices.health-data"
}
"type": "medical.medical.consolidated-data"
},
...
}
```

Expand All @@ -191,7 +189,8 @@ The format follows:
</ResponseField>

<ResponseField name="type" type="string" required>
The type of the webhook message. If coming from the Devices API, this will be formatted `devices.<subtype>`, and if coming from the Medical API it will be formatted `medical.<subtype>`.
The type of the webhook message. This can either be `ping` or one of the
[Medical API types](/medical-api/more-info/webhooks#types-of-messages).
</ResponseField>

</Expandable>
Expand Down
25 changes: 19 additions & 6 deletions docs/medical-api/more-info/webhooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ You should expect to get more than one Webhook message per patient per request (
To enable this integration approach with Metriport, and for some prerequesite reading to understand
how the Webhook flow works, see [our Webhooks guide](/home/api-info/webhooks).

### Types of Messages

- `medical.document-download`: result of Document Query, containing the newly downloaded documents
for the patient - see [details](#patient-document-data) below;
- `medical.document-conversion`: result of converting the newly downloaded C-CDA documents into FHIR -
see [details](#patient-document-data) below;
- `medical.document-bulk-download-urls`: list of download urls for a patient's documents, see
[details](#bulk-document-download-urls) below;
- `medical.consolidated-data`: result of a Consolidated Data Query, containing the patient's data in FHIR
format - see [details](#patient-consolidated-data) below.

Comment on lines +26 to +34
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while we're at it - let's add mention of these types to each endpoint page?

ie for example https://docs.metriport.com/medical-api/api-reference/document/start-document-query should mention that medical.document-download and medical.document-conversion will be sent, and we can link do this page for details

### Passing Metadata

You can pass metadata to endpoints that support webhooks, and you will receive the `meta.data` field of the webhook request.
Expand All @@ -36,23 +47,25 @@ Below is an example payload you could send in the request body of one of those e

These are messages you can expect to receive in the following scenarios:

1. When [queried documents](/medical-api/api-reference/document/start-document-query) have completed downloading (`type` for a patient in
the message will be `document-download`, and at this point you'll be able to [download the raw files](/medical-api/api-reference/document/get-document));
1. When [queried documents](/medical-api/api-reference/document/start-document-query) have completed
downloading, the message `type` will be `medical.document-download`, and at this point
you'll be able to [download the raw files](/medical-api/api-reference/document/get-document);
2. Then, if the downloaded documents contained C-CDA/XML files, when the conversion to FHIR has
completed, the message `type` will be `medical.document-conversion`, and at this point
you'll be able to query for [patient consolidated data](#patient-consolidated-data) in
FHIR-compliant format.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for consistency - let's link to the endpoint they can use to do the query as per above, not the webhook message


<Tip>
Note that the webhooks will only contain updates for new data fetched in the current document
query.
</Tip>

2. Then, if the downloaded documents contained C-CDA/XML files, when the conversion to FHIR has completed (`type` for a patient in the message
will be `document-conversion`, and at this point you'll be able to query for [patient consolidated data](#patient-consolidated-data) in FHIR-compliant format).

```json
{
"meta": {
"messageId": "<message-id>",
"when": "<date-time-in-utc>",
"type": "medical.document-bulk-download-urls"
"type": "medical.document-download"
"data": {
youCan: "putAny",
stringKeyValue: "pairsHere",
Expand Down