---
title: "Signed webhooks"
description: "Send HMAC-signed JSON events from Prosyo to any HTTPS endpoint when prospects are created, stages change or replies arrive. Code samples included."
url: https://prosyo.com/docs/integrations/webhooks/
section: "Integrations"
updated: 2026-11-06
---

# Signed webhooks

> Looking for the opposite direction, to start Prosyo outreach when something happens in another app? See [Custom triggers](https://prosyo.com/docs/integrations/custom-triggers.md).

Prosyo can **POST a JSON event** to any HTTPS URL when something happens. Use this to connect [n8n](https://prosyo.com/docs/integrations/n8n.md), [Zapier](https://prosyo.com/docs/integrations/zapier.md), [Make](https://prosyo.com/docs/integrations/make.md), a data warehouse or your own backend.

## Events

| Event | Fires when |
|---|---|
| `prospect.created` | A prospect is added by import, LinkedIn search, the Chrome extension or by hand |
| `prospect.stage_changed` | A prospect moves stage in the table, board, side panel or Inbox |
| `prospect.enriched` | AI research or a first-touch line is saved |
| `conversation.reply_received` | A LinkedIn or email reply stops the sequence |
| `campaign.launched` | A campaign starts sending |
| `campaign.paused` | A running campaign is paused |
| `account.needs_reconnect` | A LinkedIn or email account needs reconnecting |
| `intent.high_intent` | Someone unlocked in Intent who fits your ideal customer shows strong buying signals |
| `intent.playbook_matched` | An Intent playbook picks up a person. Sent only for playbooks with **Send each match to Slack, HubSpot or your webhook when those are connected** turned on. |

## Set up

You need to be a workspace **owner or admin**.

1. Create an endpoint that accepts **POST** with a JSON body. It must be **HTTPS** on the standard port (443) and publicly reachable. HTTP, other ports, private and internal addresses, and URLs with a username or password are blocked.
2. In Prosyo, go to **Integrations** and click the **Custom webhook** card. Its details open in a panel on the right.
3. Click **Connect Custom webhook**.
4. Paste the URL into **HTTPS endpoint** and click **Connect**.
5. Copy the **Signing secret** with **Copy secret**. It's shown once. You can rotate it later.
6. Under **Events**, choose the events to send. All of them are ticked at first.
7. Under **Field mapping**, choose which prospect fields appear in `data`, and type the JSON key each one should use.
8. Click **Save mapping**.
9. Click **Test connection** to send a sample.

The test event has the type `integration.test` and uses a sample person, not one of your prospects.

## Payload

```json
{
  "id": "evt_01HQEXAMPLE",
  "type": "conversation.reply_received",
  "created_at": "2026-08-17T12:00:00.000Z",
  "workspace_id": "ws_…",
  "data": {
    "prospect_id": "pr_…",
    "campaign_id": "cp_…",
    "event": "conversation.reply_received",
    "email": "alex@acme.com",
    "full_name": "Alex Rivera",
    "title": "Head of Sales",
    "company": "Acme",
    "linkedin_url": "https://www.linkedin.com/in/alexrivera",
    "stage": "replied",
    "personalized_message": "…"
  }
}
```

`data` always has `prospect_id`, `campaign_id` and `event`. The other fields inside `data` follow your field mapping. The panel also shows a **Sample event payload** you can copy with **Copy JSON**. It's only an example: live events contain the fields in your mapping plus `prospect_id`, `campaign_id` and `event`.

## Headers

| Header | Value |
|---|---|
| `Content-Type` | `application/json` |
| `X-Prosyo-Signature` | Hex HMAC-SHA256 of the raw body, using your signing secret |
| `X-Prosyo-Event` | The event type, for example `conversation.reply_received` |
| `X-Prosyo-Delivery-Id` | The ID of this delivery, the same as `id` in the body |
| `User-Agent` | `Prosyo-Webhooks/1.0` |

## Verify the signature

Compute HMAC-SHA256 of the **raw request body** with your signing secret, as lowercase hex, and compare it to `X-Prosyo-Signature`. Reject the request if they don't match.

**Node.js**

```js
import crypto from "node:crypto";

function isValidProsyoRequest(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

**Python**

```python
import hmac, hashlib

def is_valid_prosyo_request(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header or "")
```

**PHP**

```php
$expected = hash_hmac('sha256', file_get_contents('php://input'), $secret);
$valid = hash_equals($expected, $_SERVER['HTTP_X_PROSYO_SIGNATURE'] ?? '');
```

Always sign the **raw** body. Parsing the JSON and re-encoding it changes the bytes, and the signature won't match.

## Responses and retries

- Return any **2xx** status within **10 seconds** to mark the delivery successful. Reply first and do slow work afterwards.
- Failed deliveries are **retried** with growing gaps for about eight hours.
- Once your endpoint is fixed, click **Test connection** (or wait for the next event). When a delivery succeeds again, Prosyo resends the deliveries that ran out of retries in the last 30 days. Each one carries the prospect's details as they are at that moment.
- While deliveries fail, the card shows **Needs attention**. **Recent deliveries** in the panel lists the latest deliveries, and **Last delivery error** shows the reason.
- Every attempt is a new delivery with a new `X-Prosyo-Delivery-Id`, so the same event can reach you more than once. Make your workflow safe to run twice, for example by looking the person up in your own system (by `data.prospect_id` or email) and updating the record if it's already there, instead of always creating a new one.

## Rotate the secret

Open the **Custom webhook** card and click **Rotate secret**. The old secret stops working straight away, so copy the new one and update it in your workflow. Deliveries signed with the new secret fail verification until you do.
