# Webhooks

A webhook allows your application to receive real-time notifications when specific events occur. Webhooks can be configured through the PORTOS BackOffice application. For each webhook, you can define the following options:

* **URL**: address where the events will be delivered
* **Secret**: optional string that is used to create JSON payload signature.
* **Resource filter**: specifies list of resources (e.g. tickets, article categories, ...) for which notification will be sent.

When an event is triggered, the PORTOS API sends an HTTP POST request to the specified URL, carrying the event data as a JSON payload. It is essential that the external application hosting the URL can handle these HTTP POST requests to process the event data effectively.

## Testing Webhook Deliveries

1. In your browser, navigate to <https://smee.io>
2. Click **Start a new channel**. Smee.io will generate a unique Webhook Proxy URL for you.
3. Copy the full URL under "**Webhook Proxy URL**".
4. Open PORTOS BackOffice application.
5. Navigate to **System** > **Extensions** > **Webhooks** (or **Systém** > **Rozšírenia** > **Webhooky** if using the Slovak version).
6. Create a new webhook and paste the copied URL from Smee.io into the **Address** (**Adresa**) field.
7. Press the **Save** (**Uložiť**) button
8. In PORTOS BackOffice, perform an action such as creating, editing, or deleting a resource (e.g., an article category).
9. Check your Smee.io channel; the event should appear, confirming that the webhook is working correctly.

<div><figure><img src="https://2625353903-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MRdKHcwH62gIq_28fmA%2Fuploads%2FYewGzKwXxH2GryzUy1vx%2Fwebhooks-bo-settings.png?alt=media&#x26;token=d94eef0c-8246-4855-966c-8b935af8bc2a" alt=""><figcaption><p>Setup an webhook using your smee.io channel URL</p></figcaption></figure> <figure><img src="https://2625353903-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MRdKHcwH62gIq_28fmA%2Fuploads%2Fb5WNOIXadphZeYZs7TFu%2Fwebhooks-smeeio.png?alt=media&#x26;token=a7997591-6652-4a99-aa70-b3987a1aed49" alt=""><figcaption><p>The reeived webhook is displayed in your smee.io channel</p></figcaption></figure></div>

## Validating Webhook Deliveries

To ensure the security and authenticity of webhook deliveries, users have the option to specify a "**secret**" when setting up a webhook. If a secret is defined, every webhook request from your app will include an HTTP header named `X-PORTOS-WEBHOOK-SIGNATURE`. This header contains an HMACSHA256 signature of the JSON payload, generated using the secret. External applications can use this signature to validate that the webhook requests are genuinely from your app and have not been tampered with.

### Example Implementations

To implement HMAC verification in your app, please refer to examples below. Examples are using following variables:

* `jsonPayload`: The content of the HTTP request body that contains the event data in JSON format.
* `receivedSignature` : The value of the `X-PORTOS-WEBHOOK-SIGNATURE` HTTP header, which contains the HMACSHA256 signature sent by the PORTOS API.
* &#x20;`secret`: The secret key specified in webhook settings, used to generate the signature.

{% tabs %}
{% tab title="C#" %}

```csharp
using System.Security.Cryptography;
using System.Text;

public bool ValidateWebhookSignature(string jsonPayload, string receivedSignature, string secret)
{
    // Convert the secret to a byte array
    var secretKey = Encoding.UTF8.GetBytes(secret);

    // Create HMACSHA256 using the secret key
    using (var hmac = new HMACSHA256(secretKey))
    {
        // Compute the hash of the JSON payload
        var payloadBytes = Encoding.UTF8.GetBytes(jsonPayload);
        var computedHash = hmac.ComputeHash(payloadBytes);

        // Convert the computed hash to a hex string
        var computedSignature = BitConverter.ToString(computedHash).Replace("-", "").ToLower();

        // Compare the computed signature with the received signature
        return computedSignature == receivedSignature.ToLower();
    }
}

```

{% endtab %}

{% tab title="PHP" %}

```php
function validateWebhookSignature($jsonPayload, $receivedSignature, $secret)
{
    // Compute the HMACSHA256 hash of the JSON payload using the secret
    $computedHash = hash_hmac('sha256', $jsonPayload, $secret);

    // Compare the computed hash with the received signature
    return hash_equals($computedHash, $receivedSignature);
}
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import * as crypto from 'crypto';

function validateWebhookSignature(jsonPayload: string, receivedSignature: string, secret: string): boolean {
    // Create HMACSHA256 hash using the secret key
    const hmac = crypto.createHmac('sha256', secret);
    hmac.update(jsonPayload);

    // Compute the hash as a hex string
    const computedSignature = hmac.digest('hex');

    // Compare the computed signature with the received signature
    return computedSignature === receivedSignature.toLowerCase();
}
```

{% endtab %}
{% endtabs %}

### Example Hash calculation

Here’s an example of how to calculate the SHA256 hash for a webhook using the provided secret and JSON content:

**Secret**: `my-secret-key`

**JSON content**:

```json
{"id":"c32314f8-39c2-4d03-864e-201586637657","version":"1.0","event":"portos.resources.changed","data":{"user":{"name":"Majiteľ","userName":"999","featureName":null,"deviceName":"BackOffice"},"resourceName":"articleCategories","action":"Updated","resource":{"label":"PIV","description":"Pivá","customerDescription":"","courseNumber":null,"color":"#FF9800","sortHint":null,"tags":[],"ordering":null}}}
```

**Resulting SHA256 hash**: `06324bd73f157373a4a678320a014d8c3db4c2e07d8a6177ecdf233b9f07d3f0`

## Webhook payload

### Data models

The webhook content follows this data structure:

#### `WebhookPayloadDto`

| Name      | Type                                                                    | Description                                                                                                                  |
| --------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `id`      | `string`                                                                | Unique webhook identifier (GUID v4 format), generated by PORTOS API.                                                         |
| `version` | `string`                                                                | Indicates version of webhook payload. Currently fixed to "**1.0**"                                                           |
| `event`   | `string`                                                                | Name of event. Currently, only *resource changed events* are delivered, so values is fixed to "**portos.resources.changed**" |
| `data`    | [`WebhookResourceChangedContentDto`](#webhookresourcechangedcontentdto) | Based on value of "event" field, content may vary.                                                                           |

#### `WebhookResourceChangedContentDto`

| Name           | Type                                       | Description                                                                                                                                                                                              |
| -------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `user`         | [`UserInfoIdentity`](#userinfoidentity)`?` | Information about user that triggers the resource change.                                                                                                                                                |
| `action`       | `string`                                   | Name of action that represents the resource change. Supported values: **created**, **updated** or **deleted**.                                                                                           |
| `resourceName` | `string`                                   | Name of resource, e.g. ticket, articleCategory, ...                                                                                                                                                      |
| `resource`     | `object`                                   | The resource that has been created/updated/deleted. Based on value of "resourceName" field, content may vary (e.g. for "ticket", content is [`Ticket`](https://developers.portos.sk/data-models#ticket). |

#### `UserInfoIdentity`

| Name          | Type     | Description                                                                                         |
| ------------- | -------- | --------------------------------------------------------------------------------------------------- |
| `name`        | `string` | Display name of user.                                                                               |
| `userName`    | `string` | Unique user identifier.                                                                             |
| `featureName` | `string` | Name of feature (specified for "virtual" users only, e.g. user representing some Portos extension). |
| `deviceName`  | `string` | Unique name of the device on which the user operates.                                               |

### Webhook Payload Example

{% hint style="info" %}
Please note that the actual payload does not include JSON indentation.
{% endhint %}

```json
{
    "id": "c32314f8-39c2-4d03-864e-201586637657",
    "version": "1.0",
    "event": "portos.resources.changed",
    "data":
    {
        "user":
        {
            "name": "Majiteľ",
            "userName": "999",
            "featureName": null,
            "deviceName": "BackOffice"
        },
        "resourceName": "articleCategories",
        "action": "Updated",
        "resource":
        {
            "label": "PIV",
            "description": "Pivá",
            "customerDescription": "",
            "courseNumber": null,
            "color": "#FF9800",
            "sortHint": null,
            "tags":
            [],
            "ordering": null
        }
    }
}
```
