# Custom channel



**Webhooks** allow your application to receive real-time notifications about specific events happening in our platform — such as new messages or conversation updates.

You can configure webhooks directly from your **Qpien** account to subscribe to the events you want to be notified about.

---

## How It Works

1. Customer creates a webhook with endpoint they provided(`https://example.com/webhook`) and subscribes to an event or events (`NEW_INCOMING_MESSAGE`)
   

2. Our system generates an `HMAC_SECRET` and displays it once. 

3. When a new message arrives, we `POST` the event payload to your endpoint. 

4. Your system verifies the signature using your stored HMAC secret.
   
5. If verification passes and your endpoint returns `200 OK`, the webhook is marked as successful.

6. Otherwise, retries are triggered based on exponential backoff.

---

| Field           | Description                                                                                                                                  | Example                                         |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| **endpointUrl** | The HTTPS endpoint on your server where event notifications will be sent.                                                                    | `https://yourdomain.com/webhook`                |
| **events**      | The list of event types you want to subscribe to.                                                                                            | `["NEW_INCOMING_MESSAGE", "JOIN_CONVERSATION"]` |
| **status**      | Indicates whether the webhook is currently active. A disabled webhook will not receive any events.                                           | `ACTIVE` or `DISABLED`                          |
| **secret**      | The HMAC secret key used to verify webhook signatures. This is only shown once when the webhook is created — make sure to store it securely. | `9f8d1b91e23a4a0f8f4b1c8d7b2f19e1`              |

After creating the webhook, the API will respond with your unique HMAC secret.

:::warning[]
Important: This secret is visible only once — please store it safely, as it will be hidden afterward..
:::

---

## Security & Signature Verification

Every webhook event we send includes an `HMAC SHA-256` signature in the request headers, so you can confirm the request genuinely originated from our system.


### Header

Every webhook event request includes the header: 

`X-Qpien-Signature: <hexadecimal-signature>`

### Signature Generation (our side)

We generate the signature as:

`HMAC_SHA256(secret, JSON.stringify(payload))`

### Example Verification (your side)

Here’s how you can verify the webhook payload in your app


```js
import crypto from 'crypto';
import express from 'express';

const app = express();
app.use(express.json());

app.post('/webhook', (req, res) => {
  const receivedSignature = req.headers['X-Qpien-Signature'];
  const payload = req.body;

  const expectedSignature = crypto
    .createHmac('sha256', process.env.HMAC_SECRET!)
    .update(JSON.stringify(payload))
    .digest('hex');

  if (receivedSignature === expectedSignature) {
    console.log('✅ Webhook verified');
    res.sendStatus(200);
  } else {
    console.log('❌ Invalid signature');
    res.sendStatus(401);
  }
});

app.listen(3000, () => console.log('Listening on port 3000'));

```

✅ Tip: Always verify signatures to prevent unauthorized or spoofed webhook requests.

## Delivery & Retry Mechanism

When an event occurs, we send a `POST` request to your configured `endpointUrl` with the event payload.
 
 
 ### Expected Response
 
Your endpoint must respond with an HTTP `200 OK` status to confirm successful receipt.
Any other response code (4xx/5xx) or timeout will be considered a failed attempt.


### Retry Logic

We automatically retry failed webhook deliveries using **exponential backoff**:

| Attempt | Delay Before Retry           |
| ------- | ---------------------------- |
| 1       | 1 minute                     |
| 2       | 2 minutes                    |
| 3       | 4 minutes                    |
| 4       | 8 minutes                    |
| 5       | 16 minutes                   |
| 6       | 64 minutes                   |
| 7       | 128 minutes (~2 hours total) |

After the 7th failed attempt:

- The webhook status is set to **disabled**.

- No further events will be sent.

- You can re-enable the webhook manually from your **Qpien** account once the issue is fixed on your endpoint.

## Webhook Events

Each webhook event will be sent as a `POST` request to your configured `endpointUrl` with a JSON body containing the event data.

All webhook payloads share a common envelope:


```js
{
  "eventType": "NEW_INCOMING_MESSAGE",
  "timestamp": "2025-10-31T09:45:12.920Z",
  "data": {
    // event-specific payload
  }
}
```

### NEW_INCOMING_MESSAGE

Triggered when a new message is received from a customer.

```js
{
  "eventType": "NEW_INCOMING_MESSAGE",
  "timestamp": "2025-10-31T09:45:12.920Z",
  "data": {
    "messageId": "6728751b9a3b4e9f00124e98",
    "conversationId": "671cfef92ab2f6001269d7a3",
    "channel": "WHATSAPP_WEB",
    "sender": "671cfef92ab2f6001269d7a5",
    "content": "Hey, I need help with my order.",
  }
}

```

### NEW_OUTGOING_MESSAGE

Triggered when a new message is sent from your team to the customer.

```js
{
  "eventType": "NEW_OUTGOING_MESSAGE",
  "timestamp": "2025-10-31T09:47:30.211Z",
  "data": {
    "messageId": "6728753c9a3b4e9f00124eaf",
    "conversationId": "671cfef92ab2f6001269d7a3",
    "channel": "WHATSAPP_WEB",
    "sender": "671cfef92ab2fba012694ac"
    "content": "Hi John, sure! Can you please share your order number?",
  }
}

```

### JOIN_CONVERSATION

Triggered when an agent joins a conversation.

```js
{
  "eventType": "JOIN_CONVERSATION",
  "timestamp": "2025-10-31T09:50:01.000Z",
  "data": {
    "conversationId": "671cfef92ab2f6001269d7a3",
    "joinedBy": "671cfef92ab2f600bc1fab3"
  }
}
```

### CLOSE_CONVERSATION

Triggered when a conversation is closed.

```js
{
  "eventType": "CLOSE_CONVERSATION",
  "timestamp": "2025-10-31T10:05:45.112Z",
  "data": {
    "conversationId": "671cfef92ab2f6001269d7a3",
    "closedBy": "671cfef92ab2f600bab3ce",
    "status": "CLOSED"
  }
}

```

### REOPEN_CONVERSATION

Triggered when a previously closed conversation is reopened.

```js
{
  "eventType": "REOPEN_CONVERSATION",
  "timestamp": "2025-10-31T10:10:23.500Z",
  "data": {
    "conversationId": "671cfef92ab2f6001269d7a3",
    "reopenedBy": "671cfef92ab2f60012748ac52" ,
    "status": "OPEN"
  }
}

```

### UPDATE_USER

Triggered when an agent status is changed

```js
{
  "eventType": "UPDATE_USER",
  "timestamp": "2025-10-31T10:15:55.341Z",
  "data": {
    "agent": "671cfef92ab2f6001c3718ba",
    "firstName": "John",
    "lastName": "Doe"
    "status": "OFFLINE"
  }
}

```



## Best Practices

📌 Always verify **`X-Qpien-Signature`** before processing payloads..

📌 Make sure your webhook endpoint responds **within 5 seconds** to avoid timeouts.

📌 Use HTTPS for your endpoint URLs — **HTTP is not allowed**.

📌 Re-enable disabled webhooks from your account **once your endpoint is stable**..



