Webhooks let you build custom integrations on top of Partner.io. Instead of polling our API for changes, you register an HTTPS endpoint and we send it a real-time POST request whenever something you care about happens — a new lead comes in, a partner updates their details, a contact changes, and more.
This guide covers how to subscribe to events, what we send, how to verify that a request genuinely came from us, and how to build a reliable receiver.
Base URL. All management endpoints below are relative to your API base URL, for example
https://api.partner.io/v1. Replace the host with the one issued to your account.
You create a subscription — an endpoint URL plus the list of events you want to receive.
An event happens in your Partner.io account (for example, a new lead is created).
We send an HTTP POST to your endpoint with a JSON payload describing what happened.
Your endpoint responds with a 2xx status code to acknowledge receipt.
You can create multiple subscriptions — for example, one endpoint for lead events and another for partner events — and each subscription can listen to any combination of events.
The webhook management endpoints (listed below) are part of the Partner.io API and use the same authentication as the rest of the API: a bearer token sent in the Authorization header.
Authorization: Bearer YOUR_API_TOKENThe webhook deliveries we send to your endpoint are authenticated in the opposite direction — see Verifying deliveries.
A subscription has the following attributes:
Field | Type | Description |
|---|---|---|
| string | A label to help you identify the subscription. |
| string | The HTTPS endpoint we send deliveries to. Must be a valid URL. |
| string | Optional. A shared secret used to sign deliveries. Strongly recommended. |
| boolean | Whether the subscription is currently receiving deliveries. |
| integer[] | The IDs of the events to subscribe to (see Available events). |
GET /v1/webhooksReturns a paginated list (10 per page by default; override with ?per_page=).
POST /v1/webhooks
Content-Type: application/json
{
"name": "Lead pipeline sync",
"url": "https://example.com/hooks/partner-io",
"secret": "whsec_a8f5f167f44f4964e6c998dee827110c",
"is_active": true,
"events": [10, 11]
}Response 201 Created
{
"data": {
"id": 42,
"name": "Lead pipeline sync",
"url": "https://example.com/hooks/partner-io",
"is_active": true,
"events": [
{ "id": 10, "entity": "Lead", "event": "updated", "full_name": "Lead.updated" },
{ "id": 11, "entity": "Lead", "event": "deleted", "full_name": "Lead.deleted" }
],
"created_at": "2026-06-26T12:34:56.000000Z",
"updated_at": "2026-06-26T12:34:56.000000Z"
}
}The
secretis never returned in API responses. Store it securely when you create the subscription.
GET /v1/webhooks/{id}PATCH /v1/webhooks/{id}Send only the fields you want to change. Providing events replaces the subscription's event list with the IDs you send, so include the full set you want to keep.
DELETE /v1/webhooks/{id}Response 204 No Content
Events are identified by a resource and an action. Subscribe to them by their numeric id, which you can retrieve at any time from the events endpoint:
GET /v1/webhooks/eventsThe events available today are:
Resource | Actions | Fires when… |
|---|---|---|
Lead |
| A lead's details change, or a lead is removed. |
Lead Contact |
| A contact is added to a lead, edited, or removed. |
Partner |
| A partner is onboarded, their details change, or they're removed. |
Partner Rep |
| A partner rep is added, edited, or removed. |
Event Attendee |
| Someone registers for an event, their registration details change, or they're removed. |
Tip. Adding or editing a lead's contacts is also reflected as a
Leadupdateddelivery, so subscribing toLead.updatedkeeps your copy of a lead in sync as it changes over its lifetime.
For historical reasons, two resources use a different internal identifier in the GET /v1/webhooks/events catalog and in the entity field of a subscription record. Use this table to map them when selecting IDs:
Resource (as shown in deliveries) | Identifier in the events catalog |
|---|---|
Partner |
|
Partner Rep |
|
Lead |
|
Lead Contact |
|
Event Attendee | EventAttendee |
Because you subscribe by numeric id, you don't need to type these identifiers by hand — fetch the catalog, find the resource and action you want, and use its id.
When an event fires, we send an HTTP POST to your subscription's url.
Header | Value |
|---|---|
|
|
|
|
Every delivery shares the same top-level envelope:
{
"timestamp": "2026-06-26T12:34:56.000000Z",
"entity": "Lead",
"event": "updated",
"data": { }
}Field | Description |
|---|---|
| ISO 8601 (UTC) time the delivery was generated. |
| The resource type: |
| The action: |
| The full resource. See Payload reference. |
If you set a secret on a subscription, every delivery includes an X-Signature header so you can confirm it genuinely came from Partner.io and was not modified in transit.
The signature is an HMAC-SHA256 of the raw request body, keyed with your secret, and prefixed with sha256=:
X-Signature: sha256=2f1d... (64 hex characters)To verify, compute the same HMAC over the raw body you received and compare it to the header using a constant-time comparison. Always hash the raw bytes of the request body — do not re-serialize the parsed JSON, as differences in key order or spacing will produce a different signature.
const crypto = require('crypto');
// Capture the raw body so it can be hashed exactly as received.
app.use('/hooks/partner-io', express.raw({ type: 'application/json' }));
app.post('/hooks/partner-io', (req, res) => {
const signature = req.get('X-Signature') || '';
const expected =
'sha256=' +
crypto.createHmac('sha256', process.env.PARTNER_IO_WEBHOOK_SECRET)
.update(req.body) // req.body is a Buffer (the raw bytes)
.digest('hex');
const ok =
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!ok) return res.status(401).send('Invalid signature');
const payload = JSON.parse(req.body.toString('utf8'));
// ... handle the event ...
res.sendStatus(200);
});$raw = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_SIGNATURE'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $raw, $yourWebhookSecret);
if (! hash_equals($expected, $signature)) {
http_response_code(401);
exit('Invalid signature');
}
$payload = json_decode($raw, true);
// ... handle the event ...
http_response_code(200);The data object inside the envelope depends on the entity. All timestamps are ISO 8601 (UTC).
{
"timestamp": "2026-06-26T12:34:56.000000Z",
"entity": "Lead",
"event": "updated",
"data": {
"id": 90817,
"company_id": 312,
"partner_rep_id": 4521,
"name": "Globex Corporation",
"data": { "industry": "Manufacturing", "employees": "500-1000" },
"status": "approved",
"pipeline_id": "pipe_2",
"stage_id": "stage_negotiation",
"deal_value": "48000.00",
"closed_at": null,
"deal_currency": "USD",
"expected_close_date": "2026-08-15T00:00:00.000000Z",
"source": "partner",
"myshopify_domain": null,
"partner_link_id": 77,
"shopify_app_link_id": null,
"event_id": null,
"created_at": "2026-05-02T09:12:00.000000Z",
"updated_at": "2026-06-26T12:34:56.000000Z",
"customer_id": "cus_Qh12abcD",
"stat_total_revenue": 48000,
"stat_total_commission": 4800,
"stat_total_commission_pending": 1200,
"stat_currency": "USD",
"contacts": [
{
"id": 5567,
"name": "Dana Whitfield",
"fields": [
{ "type": "email", "label": "Work email", "value": "[email protected]" },
{ "type": "phone", "label": "Mobile", "value": "+1 415 555 0132" }
]
}
],
"attribute_options": [
{
"id": 14,
"group_name": "Region",
"attribute_name": "Territory",
"attribute_value": "North America"
}
]
}
}Field | Description |
|---|---|
| The lead's unique ID. |
| Your company's ID. |
| The partner rep the lead is attributed to. |
| The lead's name. |
| Custom fields captured for the lead (free-form key/value object). |
| Lead status, e.g. |
| The pipeline and stage the lead currently sits in. |
| The deal's value and currency. |
| Expected and actual close dates. |
| Where the lead originated, e.g. |
| Your external customer reference, if set. |
| Rolled-up revenue and commission totals for the lead. |
| The lead's contacts (see Lead Contact). |
| Tags/attributes assigned to the lead. |
{
"timestamp": "2026-06-26T12:34:56.000000Z",
"entity": "Lead Contact",
"event": "created",
"data": {
"id": 5567,
"name": "Dana Whitfield",
"fields": [
{ "type": "email", "label": "Work email", "value": "[email protected]" },
{ "type": "phone", "label": "Mobile", "value": "+1 415 555 0132" }
]
}
}Each entry in fields has a type (such as email or phone), a label, and a value.
{
"timestamp": "2026-06-26T12:34:56.000000Z",
"entity": "Partner",
"event": "updated",
"data": {
"id": 88,
"name": "Acme Digital",
"email": "[email protected]",
"phone": "+1 628 555 0143",
"address": "500 Howard St",
"city": "San Francisco",
"state": "CA",
"code": "94105",
"country_code": "US",
"website": "https://acmedigital.com",
"logo_url": "https://cdn.partner.io/logos/acme.png",
"profile_url": "https://app.partner.io/partners/acme-digital",
"created_at": "2025-11-01T08:00:00.000000Z",
"updated_at": "2026-06-26T12:34:56.000000Z"
}
}Field | Description |
|---|---|
| The partner's unique ID. |
| The partner's name. |
| Primary contact details. |
| Postal address ( |
| The partner's website. |
| URL of the partner's logo. |
| Link to the partner's profile in Partner.io. |
{
"timestamp": "2026-06-26T12:34:56.000000Z",
"entity": "Partner Rep",
"event": "created",
"data": {
"id": 4521,
"name": "Jane Cooper",
"email": "[email protected]",
"phone": "+1 415 555 0188",
"partner_id": 88,
"status": "active",
"created_at": "2026-06-26T12:34:56.000000Z",
"updated_at": "2026-06-26T12:34:56.000000Z"
}
}Field | Description |
|---|---|
| The partner rep's unique ID. |
| The rep's name. |
| The rep's contact details. |
| The ID of the Partner this rep belongs to. |
| The rep's status. |
{
"timestamp": "2026-06-26T12:34:56.000000Z",
"entity": "Event Attendee",
"event": "created",
"data": {
"id": "9f8c1e42-7b3a-4a91-9d2e-5c6f0b1a8d43",
"first_name": "Dana",
"last_name": "Whitfield",
"email": "[email protected]",
"phone": "+1 415 555 0132",
"company_name": "Globex Corporation"
}
}Field | Description |
|---|---|
| The attendee's unique ID. A UUID string, not an integer. |
| The attendee's first name. |
| The attendee's last name. May be |
| The attendee's email address. |
| The attendee's phone number. May be |
| The company the attendee registered under. May be |
Respond quickly with a 2xx. Your endpoint should acknowledge the delivery with any 2xx status code as soon as possible. Any other response (or a connection timeout) is treated as a failure. Do the heavy lifting — database writes, downstream API calls — asynchronously after you've responded.
Timeout. Each delivery attempt waits up to 30 seconds for a response.
Retries. A failed delivery is retried up to 3 times in total. If all attempts fail, the delivery is dropped, so design your integration to tolerate the occasional missed event (for example, by periodically reconciling against the REST API).
Expect at-least-once delivery. Because failed attempts are retried, your endpoint may occasionally receive the same event more than once. Make your handler idempotent — for example, de-duplicate on the combination of entity, data.id, and event, and ignore a delivery whose timestamp is older than the last one you processed for that resource.
Use HTTPS and a secret. Always point subscriptions at an HTTPS URL and set a secret so you can verify deliveries. Reject any request whose signature doesn't match.
Pause instead of deleting. To temporarily stop deliveries without losing the subscription, set is_active to false with a PATCH, then flip it back when you're ready.
Stand up an endpoint that accepts POST requests and returns 200.
Find the event IDs you want with GET /v1/webhooks/events.
Create the subscription with POST /v1/webhooks, including a secret.
Verify the signature on each delivery and process the payload.
Go live — your endpoint now receives events in real time.