> For the complete documentation index, see [llms.txt](https://developers.felt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developers.felt.com/rest-api/listening-to-updates-using-webhooks.md).

# Listening to updates using webhooks

A great way of building data-driven apps using Felt is by triggering a workflow whenever something changes on a map, like someone drawing a polygon around an area of interest or updating the details on a pin.

Instead of polling by listing annotations, comments or data layers on a fixed interval, set up a *webhook* and Felt will send your endpoint a notification any time a map is updated. This allows you to build integrations on top, such as sending a Slack message or recalculating statistics for a newly-drawn area.

## How webhooks work

Each webhook is attached to a single map. Whenever the map changes — annotations drawn or edited, layers updated, comments added or resolved — Felt sends a `POST` request with a JSON body to your webhook URL:

```json
{
  "attributes": {
    "type": "map:update",
    "map_id": "Jzjr8gMKSrCOxZ1OSMT49CB",
    "updated_at": "2024-04-29T12:16:46"
  }
}
```

A few things to know about the delivery model:

* **Event types.** `map:update` is currently the only event type. The payload tells you *which* map changed and *when*, but not what changed — use the [annotations](/rest-api/working-with-annotations.md), layers, or comments endpoints to fetch the current state when you receive an event.
* **Deliveries are coalesced.** A rapid burst of edits to the same map may result in a single notification rather than one per edit. Treat each delivery as "this map changed, re-read it", not as a per-edit event stream.
* **Retries.** If your endpoint does not respond with a 2xx status, delivery is retried up to 10 times with exponential backoff. This means delivery is *at least once*: your handler should be idempotent. Redirects are not followed.
* **Creation is UI-only.** Webhooks are created in workspace settings (see below); there are no REST endpoints for managing them.

## Verifying webhook signatures

Every webhook has a signing key, shown in the workspace settings UI when you create it. Felt signs each delivery with a `felt-signature` HTTP header containing the Base64-encoded HMAC-SHA256 of the raw JSON body, computed with that signing key.

Verify the signature before acting on a payload — it proves the request came from Felt and not from a third party who discovered your URL:

{% tabs %}
{% tab title="Python" %}

```python
import base64
import hashlib
import hmac

def verify_felt_signature(signing_key: str, raw_body: bytes, signature_header: str) -> bool:
    expected = base64.b64encode(
        hmac.new(signing_key.encode(), raw_body, hashlib.sha256).digest()
    ).decode()
    return hmac.compare_digest(expected, signature_header)
```

{% endtab %}

{% tab title="JavaScript (Node)" %}

```javascript
import { createHmac, timingSafeEqual } from "node:crypto";

function verifyFeltSignature(signingKey, rawBody, signatureHeader) {
  const expected = createHmac("sha256", signingKey).update(rawBody).digest("base64");
  return timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}
```

{% endtab %}
{% endtabs %}

Compute the HMAC over the raw request body exactly as received — re-serializing the parsed JSON can change key order or whitespace and produce a different signature.

## Requirements

Two things are needed in order to make use of webhooks:

1. A Felt map which will serve as the basis for the webhook. Updates will be sent whenever something on this map changes.
2. A webhook URL where the updates will be sent in the form of `POST` requests.

## Generating a new webhook

Workspace admins and editors can set up webhooks in the [Developers tab of the Workspace Settings page](https://felt.com/maps/latest/developers).

Simply click on `Create a new webhook`, select a map to listen to changes and paste in a webhook URL where the updates will be sent to.

<figure><img src="/files/xeVbieRM7ciSf6dh7goe" alt=""><figcaption></figcaption></figure>

## Using your new webhook

In order to use webhooks effectively, a receiving service must be set up to trigger actions based on the updates sent by the Felt API. Here are some examples of how to set up a webhook using Felt and an external service.

<details>

<summary>Setting up an example webhook using Pipedream</summary>

[Pipedream's RequestBin ](https://pipedream.com/requestbin)is an easy way to collect webhook requests and even run custom code as a result.

1. Create a free Pipedream account
2. On the left-hand sidebar, navigate to `Sources`, then click on `New source` in the top-right corner.
3. Select `HTTP / Webhook`, then `New Requests (Payload Only)`, and give your newly-created source a name.
4. Copy the endpoint URL. It will look like `https://XXX.m.pipedream.net`
5. In Felt, navigate to the [Developers tab of your workspace settings](https://felt.com/maps/latest/developers) and click on `Create new webhook`
6. Paste the endpoint URL from Pipedream into the `Webhook URL` text field, select your map in the dropdown and click `Create`.

To test your webhook:

1. Navigate to the Felt map that's linked to the webhook
2. Make any change: add a pin, draw with the marker, change the color of a polygon, update sharing permissions...
3. Back in Pipedream, verify that new events appear for the new source. Note that Pipedream wraps the request in its own envelope — the `body` field shown by Pipedream is the payload Felt sent:

```json
{
  "body": {
    "attributes": {
      "type": "map:update",
      "updated_at": "2024-04-29T12:16:46",
      "map_id": "Jzjr8gMKSrCOxZ1OSMT49CB"
    }
  }
}
```

4. You may also configure a script to run using the above input.

</details>

<details>

<summary>Setting up an example webhook using an AWS Lambda</summary>

Serverless functions like AWS Lambda or Google Cloud Functions are an excellent way of triggering code after a map update by setting them to run after a specific HTTP call.

1. In the AWS console, navigate to `Lambda` and click on `Create function`
2. Choose a name and runtime and, under `Advanced Settings`, make sure to check `Enable function URL`
3. Set `Auth type` to `NONE` and click on `Create function`
4. In the next screen, copy the `Function URL`. It should look like `https://{LAMBDA_ID}.lambda-url.{REGION}.on.aws`
5. Continue configuring your Lambda function as usual by editing the code that will run on map updates

In Felt:

1. Navigate to the [Developers tab of your workspace settings](https://felt.com/maps/latest/developers) and click on `Create new webhook`
2. Paste the function URL from AWS into the `Webhook URL` text field, select your map in the dropdown and click `Create`.

Since the function URL uses `Auth type: NONE`, anyone who discovers the URL can invoke it — make sure your function [verifies the `felt-signature` header](#verifying-webhook-signatures) before doing any work.

</details>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://developers.felt.com/rest-api/listening-to-updates-using-webhooks.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
