Gareth Wilson Gareth Wilson

Guide to Partnero Webhooks: Features and Best Practices

Published


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

FeatureDetails
ConfigurationProgram Settings > Webhooks, or the Webhooks API
Signaturehex HMAC-SHA256
Signature headersignature
Signed contentThe raw request body
KeyThe webhook's signing secret (signature in the API)
BodyJSON, PHP json_encode output
Event routingBody field event
Timeout5 seconds
RetriesImmediate, 1 min, 5 min, 30 min, 2 h
After retriesMarked failed in the dashboard logs

Events and payloads

EventTriggered WhenCommon Use Cases
partner.createdNew partner signs up or is createdWelcome email, mailing list, CRM
partner.updatedPartner information is updatedSync profile data
partner.approvedPartner application is approvedGrant access, notify the partner
partner.rejectedPartner application is rejectedNotify, clean up
partner.archivedPartner is archivedDeactivate downstream accounts
partner.deletedPartner is deletedRemove records
customer.createdNew customer is createdAttribute a referral
customer.updatedCustomer information is updatedSync customer data
customer.deletedCustomer is deletedRemove records
transaction.createdNew transaction is recordedUpdate CRM, accounting
transaction.updatedTransaction is updatedReconcile amounts
transaction.deletedTransaction is deleted (refund)Reverse accounting entries
reward.createdCommission/reward is createdTrack pending commissions
reward.approvedReward is approved for payoutSchedule payout reminders
reward.paidReward is marked as paidReconcile payouts

Event Payload Structure

FieldDescription
eventThe event name, e.g. transaction.created
urlThe destination URL the webhook was sent to
webhook_keyKey of the webhook subscription that fired
created_atISO 8601 timestamp of when the event was dispatched
dataEvent-specific payload

Partnero shows transaction.created data as {key, amount, currency, action, customer, partner, reward, created_at} and reward.approved data as {id, amount, currency, partner, transaction_key, approved_at}, 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

  1. In Partnero, go to Program Settings > Webhooks.
  2. Click Add Webhook and enter your endpoint URL.
  3. Select the events to receive, for example partner.created, transaction.created and reward.approved.
  4. 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_SECRET).update(req.body).digest('hex'). 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 in data, and created_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.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.