> ## Documentation Index
> Fetch the complete documentation index at: https://docs.partnero.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Outgoing webhooks

> Receive real-time notifications when events occur in Partnero

Outgoing webhooks send HTTP POST requests to your server when specific events occur in your Partnero program.

## How It Works

```
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Event     │     │  Partnero   │     │ Your Server │
│   Occurs    │────▶│  Webhook    │────▶│  Endpoint   │
│             │     │  System     │     │             │
└─────────────┘     └─────────────┘     └─────────────┘
                           │
                           ▼
                    POST /your-webhook
                    {event, url, webhook_key, created_at, data}
```

## Setting Up Outgoing Webhooks

<Steps>
  <Step title="Create your endpoint">
    Create an HTTPS endpoint on your server to receive webhook events:

    ```javascript theme={null}
    // Express.js example
    app.post('/webhooks/partnero', (req, res) => {
      const { event, data } = req.body;
      
      // Process the event
      console.log(`Received ${event} event`);
      
      // Respond quickly with 200
      res.status(200).send('OK');
      
      // Process asynchronously if needed
      processWebhookAsync(event, data);
    });
    ```

    <Note>
      To verify signatures (next step) you need the **raw, unparsed request body**. Make sure your framework gives you access to it — see the verified handler below.
    </Note>
  </Step>

  <Step title="Register in Partnero">
    1. Go to **Program Settings → Webhooks**
    2. Click **Add Webhook**
    3. Enter your endpoint URL
    4. Select which events to receive
    5. Save and copy the signing secret
  </Step>

  <Step title="Verify signatures">
    Every webhook request includes a signature in the **`Signature`** header. It is an
    `HMAC-SHA256` hash of the **raw request body**, keyed with your webhook's signing
    secret (the `signature` value shown when you created the webhook, also returned by
    the [Webhooks API](/api-reference/webhooks/overview)).

    <Warning>
      You must hash the **raw request body bytes exactly as received** — not a re-serialized
      version. Partnero signs the JSON produced by PHP's `json_encode`, which escapes forward
      slashes (`https:\/\/...`) and non-ASCII characters. Re-stringifying the parsed body
      (e.g. `JSON.stringify(req.body)`) produces different bytes and the signature will never match.
    </Warning>

    ```javascript theme={null}
    const crypto = require('crypto');

    // IMPORTANT: capture the raw body, do NOT use express.json() on this route
    app.post(
      '/webhooks/partnero',
      express.raw({ type: '*/*' }),
      (req, res) => {
        const received = req.get('Signature'); // header name is "Signature"
        const expected = crypto
          .createHmac('sha256', WEBHOOK_SECRET) // your webhook's signing secret
          .update(req.body)                     // raw Buffer, exactly as received
          .digest('hex');

        const valid =
          received &&
          received.length === expected.length &&
          crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));

        if (!valid) {
          return res.status(401).send('Invalid signature');
        }

        const payload = JSON.parse(req.body.toString('utf8'));
        // payload = { event, url, webhook_key, created_at, data }

        res.status(200).send('OK');
      }
    );
    ```

    ```php theme={null}
    <?php
    // PHP example
    $raw = file_get_contents('php://input');
    $expected = hash_hmac('sha256', $raw, $webhookSecret);

    if (!hash_equals($expected, $_SERVER['HTTP_SIGNATURE'] ?? '')) {
        http_response_code(401);
        exit('Invalid signature');
    }

    $payload = json_decode($raw, true);
    // $payload = ['event' => ..., 'url' => ..., 'webhook_key' => ..., 'created_at' => ..., 'data' => [...]]
    ```
  </Step>
</Steps>

## Available Events

### Partner Events

| Event              | Description                        |
| ------------------ | ---------------------------------- |
| `partner.created`  | New partner signs up or is created |
| `partner.updated`  | Partner information is updated     |
| `partner.approved` | Partner application is approved    |
| `partner.rejected` | Partner application is rejected    |
| `partner.archived` | Partner is archived                |
| `partner.deleted`  | Partner is deleted                 |

### Customer Events

| Event              | Description                     |
| ------------------ | ------------------------------- |
| `customer.created` | New customer is created         |
| `customer.updated` | Customer information is updated |
| `customer.deleted` | Customer is deleted             |

### Transaction Events

| Event                 | Description                     |
| --------------------- | ------------------------------- |
| `transaction.created` | New transaction is recorded     |
| `transaction.updated` | Transaction is updated          |
| `transaction.deleted` | Transaction is deleted (refund) |

### Reward Events

| Event             | Description                   |
| ----------------- | ----------------------------- |
| `reward.created`  | Commission/reward is created  |
| `reward.approved` | Reward is approved for payout |
| `reward.paid`     | Reward is marked as paid      |

## Webhook Payload

All webhooks share the same envelope. The event-specific fields are nested under `data`:

```json theme={null}
{
  "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": {}
  }
}
```

| Field         | Type   | Description                                         |
| ------------- | ------ | --------------------------------------------------- |
| `event`       | string | The event name (e.g. `transaction.created`)         |
| `url`         | string | The destination URL the webhook was sent to         |
| `webhook_key` | string | The key of the webhook subscription that fired      |
| `created_at`  | string | ISO 8601 timestamp of when the event was dispatched |
| `data`        | object | Event-specific payload (see below)                  |

### Event-Specific Payloads

<AccordionGroup>
  <Accordion title="partner.created">
    ```json theme={null}
    {
      "event": "partner.created",
      "data": {
        "id": "partner_abc123",
        "key": "john-doe",
        "email": "partner@partnero.com",
        "name": "John",
        "surname": "Doe",
        "status": "pending",
        "referral_link": "https://yoursite.com?ref=john-doe",
        "created_at": "2025-01-15T10:30:00.000000Z"
      }
    }
    ```
  </Accordion>

  <Accordion title="transaction.created">
    ```json theme={null}
    {
      "event": "transaction.created",
      "data": {
        "key": "txn_123",
        "amount": 99.99,
        "currency": "USD",
        "action": "sale",
        "customer": {
          "id": "cust_456",
          "email": "customer@partnero.com"
        },
        "partner": {
          "id": "partner_abc123",
          "email": "partner@partnero.com"
        },
        "reward": {
          "amount": 19.99,
          "currency": "USD"
        },
        "created_at": "2025-01-15T14:22:00.000000Z"
      }
    }
    ```
  </Accordion>

  <Accordion title="reward.approved">
    ```json theme={null}
    {
      "event": "reward.approved",
      "data": {
        "id": "reward_789",
        "amount": 19.99,
        "currency": "USD",
        "partner": {
          "id": "partner_abc123",
          "email": "partner@partnero.com"
        },
        "transaction_key": "txn_123",
        "approved_at": "2025-01-20T09:00:00.000000Z"
      }
    }
    ```
  </Accordion>
</AccordionGroup>

## Handling Webhooks

### Best Practices

<CardGroup cols={2}>
  <Card title="Respond Quickly" icon="bolt">
    Return a 2xx status within 5 seconds. Process data asynchronously.
  </Card>

  <Card title="Implement Idempotency" icon="shield-check">
    Handle duplicate deliveries by tracking processed event IDs.
  </Card>

  <Card title="Use HTTPS" icon="lock">
    Always use HTTPS endpoints in production.
  </Card>

  <Card title="Log Everything" icon="file-lines">
    Log all received webhooks for debugging and audit trails.
  </Card>
</CardGroup>

### Handling Retries

Partnero retries failed webhooks with exponential backoff:

| Attempt | Delay      |
| ------- | ---------- |
| 1       | Immediate  |
| 2       | 1 minute   |
| 3       | 5 minutes  |
| 4       | 30 minutes |
| 5       | 2 hours    |

<Note>
  After 5 failed attempts, the webhook is marked as failed. Check your webhook logs in the Partnero dashboard.
</Note>

### Example: Processing Partner Signup

```javascript theme={null}
app.post('/webhooks/partnero', async (req, res) => {
  const { event, data } = req.body;
  
  // Respond immediately
  res.status(200).send('OK');
  
  // Process asynchronously
  switch (event) {
    case 'partner.created':
      await sendWelcomeEmail(data.email, data.name);
      await addToMailingList(data.email);
      await notifySlack(`New partner: ${data.name} (${data.email})`);
      break;
      
    case 'transaction.created':
      await updateCRM(data);
      await syncToAccounting(data);
      break;
      
    case 'reward.approved':
      await schedulePayoutReminder(data);
      break;
  }
});
```

## Testing Webhooks

### Using the Dashboard

1. Go to **Program Settings → Webhooks**
2. Find your webhook and click **Test**
3. Select an event type
4. Click **Send Test Webhook**

### Using ngrok for Local Development

```bash theme={null}
# Start ngrok tunnel
ngrok http 3000

# Use the ngrok URL as your webhook endpoint
# https://abc123.ngrok.io/webhooks/partnero
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="Webhooks not arriving">
    * Verify your endpoint URL is correct and accessible
    * Check your server logs for incoming requests
    * Ensure your firewall allows requests from Partnero IPs
    * Check the webhook logs in your Partnero dashboard
  </Accordion>

  <Accordion title="Signature verification failing">
    * Ensure you're using the correct signing secret
    * Verify you're hashing the raw request body
    * Check for any middleware modifying the request body
  </Accordion>

  <Accordion title="Duplicate webhooks received">
    * Implement idempotency using event IDs
    * Track processed webhooks in your database
    * Return 200 status quickly to prevent retries
  </Accordion>
</AccordionGroup>

## API Reference

For programmatic webhook management, see the [Webhooks API](/api-reference/webhooks/overview).
