> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.pivolio.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.pivolio.com/_mcp/server.

# Inbound webhook

The inbound webhook is the general-purpose server-side source. If a system can
make an authenticated HTTPS POST, it can feed events into your workspace — a CRM
marking a lead qualified, an order-management system confirming a shipment, a
cron job replaying yesterday's conversions.

## Set it up

#### Create the source

Go to **Integrations** and open **Inbound webhook**, then
**Generate write key**. This mints a write key that belongs to this source
alone — it is not your SDK write key.

#### POST your events

Send each event to `/ingest/integration` with the key in the `X-Write-Key`
header. There is nothing else to configure — no signing setup, no callback
URL to register.

#### Verify

The wizard's **Verify** step waits for your first event, and the **Live
Debugger** shows every event that arrives, which person it resolved to, and
how each destination handled it.

## Authentication

The write key is the **only** credential on this endpoint.

> **Warning**
>
> There is **no request signing and no HMAC**. Anyone holding this write key can
> write events into your workspace. Treat it as a secret: store it in your
> server's secret manager, never ship it to a browser, and never commit it.
>
> This is the opposite of the first-party SDK write key, which is *designed* to be
> public because it can only ingest events from a browser context. The two keys
> are not interchangeable — presenting an SDK write key to `/ingest/integration`
> returns **403**.

## Example request

Four fields are required on every event. Omitting any of them returns a **422**:

* **`event_id`** — a unique ID for this event; used for deduplication. A ULID
  works well.
* **`event_name`** — matches `^[a-zA-Z0-9_.]{1,128}$`. Prefer a standard name so
  destinations can map it.
* **`event_time`** — Unix time in **seconds** (a float is fine). Milliseconds
  will place your event decades into the future.
* **`anonymous_id`** — the visitor ID. Forward the one the browser SDK
  established when you have it.

```bash
curl -X POST 'https://api.pivolio.com/ingest/integration' \
  -H 'X-Write-Key: wk_live_xxxxxxxxxxxx' \
  -H 'Content-Type: application/json' \
  -d '{
    "event_id": "01HQZX9K2M4N6P8R0T2V4W6Y8Z",
    "event_name": "order_completed",
    "event_time": 1700000000,
    "anonymous_id": "anon_6f1c2b0e-9a3d-4c8e-b1f7-2d5a8c9e0b31",
    "user_id": "user_456",
    "traits": { "email": "jane@acme.com", "phone": "+15551234567" },
    "properties": { "order_id": "ord_123", "revenue": 99.5, "currency": "USD" }
  }'
```

A success returns `{"ok": true, "event_id": "...", "duplicate": false}`. Resend
the same `event_id` and you get `"duplicate": true` — the event is not counted
or delivered twice, which makes retries safe.

Send raw email and phone values in `traits`. Pivolio normalizes and hashes them
server-side so every destination receives byte-identical digests; hashing them
yourself is the fastest way to quietly destroy your match rates.

## Rotating the key

Creating the webhook source again **rotates its key** — the same
**Generate write key** action, exposed as **Rotate key** once a key exists. The
old key stops working immediately, so deploy the new one to your backend in the
same change. **Remove source** deactivates the key entirely.

## Related

* [Integrations](/documentation/integrations/overview) — every available source.
* [JavaScript SDK](/documentation/integrations/java-script-sdk) — the browser source that establishes the
  `anonymous_id` you should forward here.