# Pipeline CRM Webhooks

[Pipeline CRM](https://www.pipelinecrm.com) sends a webhook when a deal, person or company is created, updated or deleted. A webhook is an automation with a **Call an API** action (or a subscription created through the API): a `POST` of a JSON payload to your URL. Deliveries can be signed with Standard Webhooks (`webhook-id`, `webhook-timestamp`, `webhook-signature`). **Signing is opt-in per endpoint.**

## When to Use This Skill

* How do I receive Pipeline CRM webhooks?
* How do I verify a Pipeline CRM webhook signature?
* How do I enable Request signing and get the `whsec_` secret in Pipeline CRM?
* How do I handle `deal.create`, `deal.update` or `person.create` events?
* Why does Pipeline CRM webhook verification fail on retries or replays?
* Why are my Pipeline CRM deliveries unsigned?

## Verification (core)

| Header | Value |
| --- | --- |
| `webhook-id` | Unique delivery ID, the same on every retry |
| `webhook-timestamp` | Unix seconds when **this attempt** was sent |
| `webhook-signature` | `v1,<base64 HMAC-SHA256>`, space-separated when two secrets are active (rotation) |

Signed content is `{webhook-id}.{webhook-timestamp}.{raw body}`. The key is the base64-**decoded** part of the secret after `whsec_`. The examples use the `standardwebhooks` library (npm, PyPI), which compares in constant time against every `v1,` entry:

```javascript
const { Webhook } = require('standardwebhooks');

// rawBody: exact request body string, NOT re-serialised JSON
function verifyPipelineCrmWebhook(rawBody, headers, secret = process.env.PIPELINE_CRM_WEBHOOK_SECRET) {
  return new Webhook(secret).verify(rawBody, {
    'webhook-id': headers['webhook-id'],
    'webhook-timestamp': headers['webhook-timestamp'],
    'webhook-signature': headers['webhook-signature'],
  }); // returns the parsed event; throws → respond 400
}

```

Python: `from standardwebhooks.webhooks import Webhook`, then `Webhook(secret).verify(await request.body(), headers)`.

### Hookdeck retries and replays hit the 5-minute window

`standardwebhooks` (and `svix`) **always** enforce a fixed 5-minute timestamp window. Hookdeck retries and replays resend the original request, including the **original** `webhook-timestamp`, so the library rejects any retry or replay that arrives more than 5 minutes after Pipeline CRM sent it. If you receive through Hookdeck, or replay old events, use the dependency-free verifier in [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/pipeline-crm-webhooks/references/verification.md) and drop or widen the window there. Pipeline CRM does not enforce the window for you either, so dedupe on `webhook-id` instead.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/pipeline-crm-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/pipeline-crm-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/pipeline-crm-webhooks/examples/fastapi/).

## Setup: signing is opt-in

Signing is opt-in: click **Enable signing** under **Request signing** on the automation's **Call an API** action and paste the secret (shown **once**) into `PIPELINE_CRM_WEBHOOK_SECRET`. Without it, deliveries are unsigned and fail verification (the handlers return 400 for missing signature headers). Steps and rotation are in [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/pipeline-crm-webhooks/references/setup.md).

## Event Payload

There is no event type string. The body carries `event_model` (`deal`, `person`, `company`) and `event_action` (`create`, `update`, `destroy`). Dispatch on `{event_model}.{event_action}`:

```json
{ "trigger": "Deal stage changed", "timestamp": "2026-09-03 14:22:07",
  "event_model": "deal", "event_action": "update", "event_model_id": 4521,
  "event_data": { "id": 4521, "name": "Acme Corporation - Q4 renewal", "deal_stage_id": 4 },
  "event_changes": { "deal_stage_id": [3, 4] },
  "caused_by_user": { "id": 13, "email": "sam.okafor@northwind.example" } }

```

## Common Event Types

| Event | Fired when |
| --- | --- |
| `deal.create` / `deal.update` / `deal.destroy` | A deal is created, changed (`event_changes` maps field to `[old, new]`) or deleted |
| `person.create` / `person.update` / `person.destroy` | A person is created, changed or deleted |
| `company.create` / `company.update` / `company.destroy` | A company is created, changed or deleted |

> **Full details:** [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/pipeline-crm-webhooks/references/overview.md) and [Pipeline CRM's API docs](https://app.pipelinecrm.com/api/docs).

## Delivery Semantics

* Return any `2xx` within a few seconds; the response body is ignored. Enqueue and process asynchronously.
* Failures retry with increasing delays, up to 16 attempts over about a day. `3xx` is not followed (register the final URL) and `410 Gone` switches the endpoint off.
* Every attempt of a delivery carries the same `webhook-id`; deliveries can arrive out of order, so read the record back from the API where order matters.

## Environment Variables

```bash
PIPELINE_CRM_WEBHOOK_SECRET=whsec_xxxxx   # shown once when you click Enable signing; handlers fail closed (500) without it

```

## Local Development

```bash
# Express / Next.js (use 8000 for FastAPI). No install or account required.
npx hookdeck-cli listen 3000 pipeline-crm --path /webhooks/pipeline-crm

```

The automation can only be saved after **Test** reaches your URL and gets a `2xx`, so start the receiver first.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/pipeline-crm-webhooks/references/overview.md) - Events, payload fields, delivery and retries
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/pipeline-crm-webhooks/references/setup.md) - Automation setup, Enable signing, rotation, Hookdeck
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/pipeline-crm-webhooks/references/verification.md) - Library and manual verification, replay window, debugging