--- title: "Receive events from another system" description: "A webhook gives a project a URL. Another system posts events to the URL, and the filters decide which events become data points." canonical: https://past.dev/docs/memory-api/webhooks last-updated: 2026-10-02 --- # Receive events from another system > A webhook gives a project a URL. Another system posts events to the URL, and the filters decide which events become data points. Product: past.dev Memory API. Source: https://past.dev/docs/memory-api/webhooks A webhook belongs to one project. You create it in the console, and past.dev gives it a URL. Paste the URL into the system that sends the events: an issue tracker, a CRM, a chat tool or your own application. past.dev counts and logs each event that the URL receives. An event that passes the filters becomes a data point. An event that does not pass is dropped. Flex and Enterprise include webhooks. Pay as you go does not. A self-hosted deployment has no plan and includes them. [Pricing](/pricing) lists the plans. ### Create a webhook 1. Open the Webhooks screen of the console and select the project in the header. 2. Select New webhook and type a name of 1 to 64 characters. 3. Set Content to the JSON path of the text. `$` is the whole body. 4. Optional: set Occurred at to the path of the event time, and Id to the path of the sender's identifier. 5. Optional: select the audience that can read the data points. 6. Optional: add filters under Only when. 7. Select Create webhook and copy the URL. 8. Paste the URL into the system that sends the events. A member who reaches the project at Write creates, edits, pauses and deletes webhooks, and reveals the URL. A member at Read sees the list with the token hidden. A project holds a maximum of 20 webhooks. ### The URL ```url https://app.past.dev/api/v1/webhooks/{webhookId}/{token} ``` The console gives the complete URL. Use it as it is given: on the managed service its host is the host of the console, and on a self-hosted deployment it is the host of the deployment. The webhook id starts with `whk_`. The token has 32 characters and is the credential: the route takes no API key, and a system that has the URL can send to it. Keep the URL secret. past.dev does not check a signature from the sender. The console hides the token and shows it again when a member at Write selects Reveal. To retire a URL, delete the webhook. ### Mapping The mapping says which parts of the event body become the data point. | Setting | What it reads | | --- | --- | | **Content** (required) | The JSON path of the text. `$` sends the whole body as text. A string is used as it is. A number, a boolean, an object or an array is used as its JSON text, as the sender wrote it. An event whose path resolves to nothing, or to empty text, is dropped. | | **Occurred at** (optional) | The JSON path of the event time: ISO 8601 text, or a Unix time in seconds or in milliseconds. When it is not set, or the value cannot be read, the time of receipt is used. | | **Id** (optional) | The JSON path of the sender's identifier, 1 to 200 characters. It becomes the `id` of the data point, so an event that is sent again replaces its data point. When it is not set, the event id is used and each event is a new data point. | | **Audience** (optional) | One audience of the project. Each data point of the webhook is scoped to it. When it is not set, the data points are project-visible. | A path starts with `$` and continues with `.name`, `['name']` or `[n]` segments, up to 500 characters: `$.issue.fields.summary`, `$.items[0].text`. A `.name` segment holds letters, digits and `_`, and does not start with a digit. Use `['name']` for any other name, such as `$['issue-key']`. Wildcards, `..`, filter expressions, slices and unions are refused when the webhook is saved. A body that is not JSON is used whole as the content. ### Filters A filter is a JSON path, an operator and the values of the operator. An event is ingested only when all the filters match. A webhook with no filter ingests each event. A webhook holds a maximum of 20 filters. | Operator | Matches when | | --- | --- | | `is` | The value at the path is equal to the one value. A string is compared exactly, and a number or a boolean by its JSON text. An object or an array does not match. | | `is any of` | The value at the path is equal to one of 1 to 50 values. | | `contains` | The string at the path contains the value, or the array at the path has an element equal to the value. | | `exists` | The path resolves to a value that is not `null`. This operator takes no value. | Comparison is case-sensitive. A filter on a path that the body does not carry does not match, and no filter matches a body that is not JSON. ### What the sender receives The sender uses `POST` with any `Content-Type` and a body of 1 MiB or less. The response says what became of the event: `202` for an ingested event and `200` for a dropped event. ```202accepted,ingested { "eventId": "evt_k3m7q2xw5n6r4t2v", "requestId": "req_h3k7m2qx5nwr", "verdict": "ingested", "droppedBy": null, "ingestionId": "7d9f2b6a-4c1e-4f8a-9b3d-2e5c8a1f6d40", "dataPointId": "SUP-1042", "unchanged": null } ``` ```200ok,dropped { "eventId": "evt_b5h2w7c4y3p6d2ks", "requestId": "req_w6t2b4zc7yda", "verdict": "dropped", "droppedBy": "$.webhookEvent is jira:issue_updated", "ingestionId": null, "dataPointId": null, "unchanged": null } ``` `eventId` starts with `evt_` and identifies the event. `dataPointId` is the id of the data point: the mapped id, or the event id when no id is mapped. `ingestionId` is the id that `GET /api/v1/ingest/{ingestionId}` takes, so completion is read the same way as for a send. `droppedBy` names the first filter that did not match, or reads `content path resolved to nothing`. An event that is sent again with the same id, content and time returns `202` with `unchanged: true`, and nothing is processed again. Map Id and Occurred at for a sender that repeats events. Without a time path, the time of receipt dates the event, and a repeat replaces the data point with a new revision. A data point from a webhook behaves as a data point from `POST /api/v1/ingest`. Its `metadata` is `{"source": "webhook", "webhookId": "whk_..."}`, and recall returns it on the sources of the data point. ### Credits - Each event received costs 0.1 credit, ingested or dropped. - An ingested event also costs the ingestion credits of its content: one credit per 350 bytes, at least one. - An event that repeats an unchanged data point costs 0.1 credit only. - A refused event costs nothing. ### Refusals | Response | When | | --- | --- | | **404** (not-found) | The webhook id or the token is wrong, or the webhook is deleted. A wrong token never returns `401`. | | **409** (webhook-paused) | The webhook is paused. Events sent during the pause are not kept, and a resume does not recover them. | | **413** (webhook-body-too-large) | The body is larger than 1 MiB. | | **402** (webhooks-not-included) | The plan of the organization does not include webhooks. | | **402** (out-of-credits) | The credits of the organization are spent. | | **429** (project-cap-reached / spend-cap-reached) | A cap of the project or of the account is reached. | | **429** (no body) | One webhook is past its limit of 600 events in one minute. The sender must wait and send again. | | **400** (audience-unknown) | The audience of the webhook was deleted. Edit the webhook and select another audience. | | **503** (organization-not-ready / pipeline-disabled) | The organization is not ready, or background ingestion is disabled on the deployment. | The Requests screen of the console lists each event received, with its request id, the webhook and the verdict. The path in the row carries the webhook id and never the token. An event that the per-minute limit refuses has no row.