Guide to Partnero Webhooks: Features and Best Practices
Partnero runs affiliate, referral and partner programmes for SaaS and e-commerce businesses: it signs up partners, attributes customers to them, records transactions and calculates commissions. That data usually needs to reach other systems, such as a CRM for new partners, an accounting tool for commissions or a payout process for rewards. Partnero's outgoing webhooks send each change as it happens.
This guide covers Partnero's 15 programme events, the envelope and its missing event ID, verifying the Signature header against PHP-encoded JSON, and the 5-second response window.
What are Partnero webhooks?
Webhooks belong to a programme. You add them under Program Settings > Webhooks, or through Partnero's Webhooks API, and choose which partner, customer, transaction and reward events each one receives. The dashboard can also send a test webhook for a chosen event.
Each delivery is a JSON POST with the event name, the url it was sent to, the webhook_key of the subscription, a created_at time and an event-specific data object.
Partnero signs every request with HMAC-SHA256 over the raw body and puts the hex digest in a header called simply Signature.
Partnero webhook features
| Feature | Details |
|---|---|
| Configuration | Program Settings > Webhooks, or the Webhooks API |
| Signature | hex HMAC-SHA256 |
| Signature header | signature |
| Signed content | The raw request body |
| Key | The webhook's signing secret (signature in the API) |
| Body | JSON, PHP json_encode output |
| Event routing | Body field event |
| Timeout | 5 seconds |
| Retries | Immediate, 1 min, 5 min, 30 min, 2 h |
| After retries | Marked failed in the dashboard logs |
Events and payloads
| Event | Triggered When | Common Use Cases |
|---|---|---|
partner.created | New partner signs up or is created | Welcome email, mailing list, CRM |
partner.updated | Partner information is updated | Sync profile data |
partner.approved | Partner application is approved | Grant access, notify the partner |
partner.rejected | Partner application is rejected | Notify, clean up |
partner.archived | Partner is archived | Deactivate downstream accounts |
partner.deleted | Partner is deleted | Remove records |
customer.created | New customer is created | Attribute a referral |
customer.updated | Customer information is updated | Sync customer data |
customer.deleted | Customer is deleted | Remove records |
transaction.created | New transaction is recorded | Update CRM, accounting |
transaction.updated | Transaction is updated | Reconcile amounts |
transaction.deleted | Transaction is deleted (refund) | Reverse accounting entries |
reward.created | Commission/reward is created | Track pending commissions |
reward.approved | Reward is approved for payout | Schedule payout reminders |
reward.paid | Reward is marked as paid | Reconcile payouts |
Event Payload Structure
| Field | Description |
|---|---|
event | The event name, e.g. transaction.created |
url | The destination URL the webhook was sent to |
webhook_key | Key of the webhook subscription that fired |
created_at | ISO 8601 timestamp of when the event was dispatched |
data | Event-specific payload |
Partnero shows transaction.created data as {key, amount, currency, action, customer, partner, reward, created_ and reward.approved data as {id, amount, currency, partner, transaction_, but only partner.created has a full example, and the other 12 events have none. Read data defensively.
Partnero's guidance says to deduplicate on event IDs, yet the envelope has no event ID. The nearest stable values are the resource's own ID or key inside data, plus event and created_at.
Published payload example
A complete partner.created delivery from the webhook samples library.
{
"event": "partner.created",
"url": "https://your-server.com/webhooks/partnero",
"webhook_key": "wh_abc123",
"created_at": "2025-01-15T10:30:00.000000Z",
"data": {
"id": "partner_abc123",
"email": "partner@partnero.com",
"name": "John",
"surname": "Doe",
"status": "approved",
"created_at": "2025-01-15T10:30:00.000000Z",
"metadata": {}
}
}
Setting up Partnero webhooks
Prerequisites
- A Partnero programme you can edit
- A public HTTPS endpoint
Register Your Endpoint
- In Partnero, go to Program Settings > Webhooks.
- Click Add Webhook and enter your endpoint URL.
- Select the events to receive, for example
partner.created,transaction.createdandreward.approved. - Save and copy the signing secret.
With the Webhooks API, the create response includes the webhook's signature value, which is the same signing secret.
Get Your Signing Secret
Store the secret as WEBHOOK_SECRET and use it as UTF-8 bytes, as shown, without hex- or base64-decoding. Each webhook has its own secret, so programmes with several webhooks need one verifier per secret.
Test a delivery
Start a local receiver and forward requests with the Hookdeck CLI:
npx hookdeck-cli listen 3000 partnero --path /webhooks/partnero
Add a webhook with the CLI's URL, then use the dashboard's test option to send a partner.created event, or create a test partner.
Securing Partnero webhooks
Compute HMAC-SHA256 of the raw body with the signing secret, hex-encode it, and compare it with Signature in constant time. Partnero's Node sample is createHmac('sha256', WEBHOOK_. Because the body comes from PHP's json_encode, slashes and non-ASCII characters are escaped (/ becomes \/, é becomes \u00e9), and JSON.stringify of the parsed object won't reproduce them. See the outgoing webhooks guide.
const crypto = require('node:crypto');
function verifySignature(rawBody, headers, secret) {
if (!Buffer.isBuffer(rawBody) || typeof secret !== 'string' || !secret) return false;
const signature = headers['signature'];
if (typeof signature !== 'string') return false;
const key = Buffer.from(secret, 'utf8');
const message = rawBody;
const candidates = [signature];
const expected = crypto.createHmac('sha256', key).update(message).digest();
return candidates.some((candidate) => {
if (!/^[0-9a-fA-F]{64}$/.test(candidate)) return false;
const received = Buffer.from(candidate, 'hex');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
});
}
The handler builds its dedup key from the event and the resource identifier in data, and routes rewards to a payouts service.
const express = require('express');
function createPartneroApp({ claim, crm, ledger, payouts }) {
const app = express();
app.post('/webhooks/partnero', express.raw({ type: '*/*' }), async (req, res) => {
const secret = process.env.WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500);
if (!verifySignature(req.body, req.headers, secret)) return res.sendStatus(401);
let parsed;
try {
parsed = JSON.parse(req.body.toString('utf8'));
} catch {
return res.sendStatus(400);
}
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return res.sendStatus(400);
const { event, created_at, data } = parsed;
if (typeof event !== 'string' || !data || typeof data !== 'object') return res.sendStatus(400);
try {
const resourceId = data.id || data.key;
if (!(await claim(`${event}:${resourceId}:${created_at}`))) return res.sendStatus(200);
const [resource] = event.split('.');
if (resource === 'partner') await crm.syncPartner(event, data);
else if (resource === 'transaction') await ledger.apply(event, data);
else if (resource === 'reward') await payouts.update(event, data);
else if (resource === 'customer') await crm.syncCustomer(event, data);
else console.log('Unhandled Partnero event', event);
} catch {
return res.sendStatus(503);
}
return res.sendStatus(200);
});
return app;
}
With a 5-second limit, the CRM and ledger calls should run in a background job in production; the handler shows the routing, not the timing.
Only the body is signed, and created_at isn't covered by a documented tolerance, so a captured request verifies whenever it's replayed. The dedup key stops it taking effect twice.
On a Partnero source, Hookdeck verifies the Signature header against the raw body using your signing secret. Partnero always receives 200, so slow processing on your side never uses up its five attempts; a bad signature is logged as failed verification and not forwarded. No timestamp tolerance applies.
Make Partnero webhooks production-ready. Hookdeck Event Gateway replies to Partnero within its 5-second window, verifies every programme event, and lets you replay commission and reward updates after an outage.
Partnero webhook limitations and pain points
A 5-second response window
The Problem: Deliveries fail while your handler is still working, and the same event is retried.
Why It Happens: Partnero expects a 2xx within 5 seconds, and CRM or accounting calls can take longer.
Workarounds:
- Persist the body and respond before calling other systems.
How Hookdeck Can Help: Hookdeck acknowledges immediately and gives your destination its own timeout.
Retries end after about two and a half hours
The Problem: A longer outage leaves a webhook marked failed, with events missing downstream.
Why It Happens: Partnero tries immediately and then after 1, 5 and 30 minutes and 2 hours, then marks the webhook failed in its logs.
Workarounds:
- Watch the dashboard logs for failed webhooks.
- Reconcile partners, transactions and rewards through the Partnero API after an incident.
How Hookdeck Can Help: Hookdeck accepts each event on the first try and stores it, so your service can catch up whenever it's back.
No event ID
The Problem: Retries can be processed twice.
Why It Happens: The envelope has webhook_key and created_at but no unique event identifier, despite the docs recommending deduplication by event ID.
Workarounds:
- Combine
event, the resource ID or key indata, andcreated_at. - Make updates set state rather than increment it.
How Hookdeck Can Help: Hookdeck deduplication can match on chosen body fields to drop identical retries.
PHP-encoded JSON
The Problem: Signatures don't match in Node or Python even with the right secret.
Why It Happens: Partnero escapes slashes and non-ASCII characters, so any re-serialised body differs from the bytes that were signed.
Workarounds:
- Read the body as raw bytes and verify before parsing.
How Hookdeck Can Help: Hookdeck verifies the original bytes and forwards them unchanged.
Best practices
Treat transaction.deleted as a refund
Partnero describes a deleted transaction as a refund. Reverse any commission or revenue entries you created from transaction.created.
Separate partner data from money
Route partner and customer events to your CRM and transaction and reward events to finance, ideally through separate queues.
Don't depend on IP allowlisting
Partnero's troubleshooting mentions allowing its IPs but publishes no list. Rely on the signature.
Respond first, then process
Store the event, return 2xx, and do the work later, as in our asynchronous processing guide. Record each dedup key with its effect, following the idempotency guide.
Conclusion
Partnero's webhooks cover the full life of an affiliate programme, from partner sign-up to reward payment, with a simple HMAC on the raw body. The things to plan for are the 5-second window, a retry schedule that ends after a couple of hours, PHP-escaped JSON and the lack of an event ID.
Hookdeck Event Gateway answers Partnero instantly and keeps every programme event stored and replayable.
Get started with Hookdeck for free and handle Partnero webhooks reliably in minutes.