---
title: Webhook Integration (Outgoing Events)
slug: salpulse-user-guide/webhook-integration-outgoing-events
docTags: 
createdAt: 2026-10-06T19:26:17.909Z
---

Salpulse can post events to a webhook URL so, with the addition of a simple middleware layer, you can [implement a webhook handler](docId:2RGo_GrKbIKHY18nJiadk) to add the Salp feed to your workflow applications (eg: Microsoft Teams). You should use a software developer to implement this.

## Configuring a Webhook

:::hint{type="info"}
Note: Configuration of integrations is restricted to your organisation's Salpulse Administrator(s) and Restricted Administrator(s).
:::

To configure a webhook integration, from the ***Integrations*** tab of the [Admin](https://app.salpulse.com/admin) menu item, click the **Add Integration** button.

::Image[]{src="https://api.archbee.com/api/optimize/JIBbX60uyU8EEhh2IsyiQ/Xt1Rl_NY9nZAevuiFuaZn_image.png" size="50" isUploading="false" width="740" height="283" darkWidth="740" darkHeight="283" position="flex-start" showCaption="false"}

In the dialog, select ***Webhook*** as the integration type, name the integration and click the ***Add Integration*** button.

::Image[]{src="https://api.archbee.com/api/optimize/JIBbX60uyU8EEhh2IsyiQ/deNcMmmkZ1wGBIFZk499H_image.png" size="50" isUploading="false" width="794" height="527" darkWidth="794" darkHeight="527" position="flex-start" showCaption="false"}

The integration will now appear in the integrations table. Hover over the integration's name until the gear icon appears. Click this icon to configure the webhook.

![](https://api.archbee.com/api/optimize/JIBbX60uyU8EEhh2IsyiQ/fv5Q76qOldL2n5QjrOTKl_image.png)

In the configuration dialog, click on the ***Webhook Secret*** field's reveal icon, which will also copy the unique secret string to the clipboard.

::Image[]{src="https://api.archbee.com/api/optimize/JIBbX60uyU8EEhh2IsyiQ/BwopPvoJmAgGJy2LFIxAY_image.png" size="60" isUploading="false" width="996" height="911" darkWidth="996" darkHeight="911" position="flex-start" showCaption="false"}

In your webhook handler, implement [HMAC Signature](docId:2RGo_GrKbIKHY18nJiadk) validation using this secret. Keep this secret private.

Then enter the URL of your webhook handler and click the plane icon to save and verify the URL. This will send a test message to your handler.

::Image[]{src="https://api.archbee.com/api/optimize/JIBbX60uyU8EEhh2IsyiQ/bFlEsb2q-v4TkENihwV6y_image.png" size="70" isUploading="false" width="1060" height="872" darkWidth="1060" darkHeight="872" position="flex-start" showCaption="false"}

On clicking the plane icon, you will be presented with the result of your test. For example.

::Image[]{src="https://api.archbee.com/api/optimize/JIBbX60uyU8EEhh2IsyiQ/ZuetPjYZarybuwHaCBxrp_image.png" size="60" isUploading="false" width="969" height="193" darkWidth="969" darkHeight="193" position="flex-start" showCaption="false"}

::Image[]{src="https://api.archbee.com/api/optimize/JIBbX60uyU8EEhh2IsyiQ/pYv3SdvcN9aGgCiXSkk7X_image.png" size="60" isUploading="false" width="938" height="170" darkWidth="938" darkHeight="170" position="flex-start" showCaption="false"}

Once you have correctly validated your webhook URL, select the events you want to subscribe to and close the configuration dialog.

::Image[]{src="https://api.archbee.com/api/optimize/JIBbX60uyU8EEhh2IsyiQ/JOjurKqHnE_J4YVGI_P3n_image.png" size="70" isUploading="false" width="958" height="217" darkWidth="958" darkHeight="217" position="flex-start" showCaption="false"}

From the integrations table, enable the newly configured webhook integration.

![](https://api.archbee.com/api/optimize/JIBbX60uyU8EEhh2IsyiQ/1wumlcechsfVzkIr9nayk_image.png)

This will start events flowing to your webhook handler.

:::hint{type="info"}
Note: You may configure up to 4 webhook integrations. They may all be subscribed to the same event(s). The reason you might choose to do this is if you have different webhook handlers doing different things. eg: one webhhook handler pushing Salps to your Microsoft Teams stream and another handler logging Salps to your server logs.
:::

## Monitoring a Webhook

You can see the the status of the last event sent to your handler by opening the configuration dialog for the integration. If the process is experiencing errors, these will be surfaced here.

::Image[]{src="https://api.archbee.com/api/optimize/JIBbX60uyU8EEhh2IsyiQ/ygFjSJdujos1UBztdI6gw_image.png" size="70" isUploading="false" width="995" height="1221" darkWidth="995" darkHeight="1221" position="flex-start" showCaption="false"}

:::hint{type="info"}
Note: Your handler must use https\:// protocol
:::

:::hint{type="info"}
Note: Salpulse's event flusher runs once per minute, so allow 1-2 minutes for events to appear at your handler - eg: after posting a new Salp.
:::

## Retries

Many errors in sending events to your handler, are considered temporary, and Salpulse will retry sending the same event. The retry protocol is:

| **Send Attempt** | **If Fails, Salpulse will Retry in** |
| ---------------- | ------------------------------------ |
| 1                | 1 min                                |
| 2                | 5 min                                |
| 3                | 15 min                               |
| 4                | 1 hour                               |
| 5                | 4 hours                              |
| 6                | 1 day                                |
| 7                | 3 days                               |
| 8                | permanent failure                    |

However, the following errors are treated as **permanent failures** and Salpulse will not retry sending the event.

| **Error Code** | **Error**             |
| -------------- | --------------------- |
| 400            | Bad request           |
| 401            | Unauthorised          |
| 403            | Forbidden             |
| 404            | Not found             |
| 422            | Unprocessable content |

In your handler, you can identify a retry via the `X-Salpulse-Event-Id` and `X-Salpulse-Delivery-Try` elements in the message header. It is the webhook handler's responsibility to ensure that if it receives multiple messages with the same event Id, that it does not double up on the processing of that event.

## Webhook Handler Implementation Notes

Your webhook handler can be implemented in any programming language. In this section, for demonstration purposes, we use NodeJS.

Ensure your webhook handler responds to messages on the https\:// protocol.

Events are sent to your handler using the `POST` method.

Each HTTPS webhook request includes the following headers:

:::BlockQuote
Content-Type: application/json
X-Salpulse-Event-Type: \<webhook.validateURL | salpCreated>
X-Salpulse-Signature: \<HMACsignature>
X-Salpulse-Event-Id: \<string> (a unique identifier for this event)
X-Salpulse-Org-Id: \<string> (a unique identifier for your organisation)
X-Salpulse-Org-Name: \<string> (the name of your organisation)
X-Salpulse-Integration-Id: \<string> (used internally by Salpulse)
X-Salpulse-Delivery-Try: \<number> (1, 2, ... 8)
:::

### HMAC Signature

Before processing the received message, calculate an HMAC-SHA256 signature of the webhook request payload, generated using the integration secret (which you have copied from the integration configuration screen), then compare it to the `X-Salpulse-Signature` header.

```javascript
  import crypto from 'crypto';
  
  ...
  
  const body = JSON.stringify(payload);
  const signature = crypto
    .createHmac('sha256', storedSecret)
    .update(body)
    .digest('hex');
    
  if (signatureInMessage !== signature) {
    res.writeHead(401);
    return res.end('Invalid Signature');
  }
```

NOTE: Calculate your HMAC signature against the **exact raw request body received**, rather than parsing the JSON and then re-stringifying it. Re-stringifying can potentially produce a different string and therefore a different signature.

### Response Time

You must respond to the webhook message within 10 seconds or Salpulse will register a temporary failure and start it's [Retry Protocol](docId:2RGo_GrKbIKHY18nJiadk). It's a good idea to collect the complete message, check the signature, check for duplicate messages, then reply to Salpulse **before** processing the message's payload.

### Message Body

The body of the message is in JSON and is structured as follows.

**Event - webhook.validateURL**

This event is part of the integration configuration flow and is designed to test that your webhook handler is properly configured and responding to events. Following is the payload structure

```json
{ 
  "eventType": "webhook.validateURL",
  "eventId": "<string>",
  "timestamp": "<ISO 8601 timestamp>"
}
```

When you receive this message, check the signature then respond immediately with a 200 (OK) code.

**Event - salpCreated**

This event is sent when one of your employees in your organisation's Salpulse posts a Salp, if your integration is subscribed to this event. There are 2 types of Salps.

- Story Salp - One author and not directed to a specific person
- Recognition Salp - One author and directed to one or more specific people

Following is the payload structure.

```javascript
{
  "eventId": "<string>",
  "eventType": "salpCreated",
  "timestamp": "<ISO 8601 timestamp>",
  "orgId": "<string>",
  "orgName": "<string>",
  "about": {
    "category": "why | way | do",
    "type": "<string>",
    "title": "<string>"
  },
  "salpType": "Story | Recognition",
  "author": {
    "name": "<string>",
    "email": "<string> | NoEmail"
  },
  "recipients": [ // Optional
    {
      "name": "<string>",
      "email": "<string> | NoEmail"
    },
    ...
  ],
  "content": "<content>",
  "media": [ // Optional
    {
      "type": "image",
      "source": "<url>"
      "height": <number>,
      "width": <number>,
    },
    ...
  ],
  "openInSalpulse": "<url>",
  "createdAt": "<ISO 8601 timestamp>"
}
```

| **Field**                         | **Detail**                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| eventId                           | A unique identifier for this event. Retries will use the same eventId. Should match the header                                                                                                                                                                                                                                                                                                                                                                                     |
| eventType<br />orgId<br />orgName | Should match the header                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| timestamp<br />createdAt          | Date and Time are in UTC time.<br />eg: "2026-10-07T19:29:20.323Z". *timestamp* is the time of delivery of this attempt. *createdAt* is the time the Salp was first created.                                                                                                                                                                                                                                                                                                       |
| about.category                    | Which channel this Salp is targeted at. eg: "way".                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| about.type                        | The text to display that describes this category, eg: "Value". The defaults are:<br />- "Purpose" for the *why* channel<br />- "Value" for the *way* channel<br />- "Shared Ambition" for the *do* channel.<br />These are configured, per organisation and may be different to the defaults. eg: "Mission", "Principle", "Shared Goal". Your handler code should use *about.category&#x20;*&#x74;o determine the channel as *about.type* may be changed by your administrator(s). |
| about.title                       | The text that identifies the specific channel or sub-channel. eg: "Care for each other" (an example title of one of the *way&#x20;*&#x73;ub-channels) or "Shaping futures through excellence in manufacturing" (an example title of the *why&#x20;*&#x63;hannel).                                                                                                                                                                                                                  |
| salpType                          | "Story" - No *recipients* field in the message<br />"Recognition" - *recipients* field will exist in the message                                                                                                                                                                                                                                                                                                                                                                   |
| author.name                       | Name of the author in the format *"first last"*.                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| author.email                      | Email address of the author or "NoEmail" if the address is unknown to Salpulse.                                                                                                                                                                                                                                                                                                                                                                                                    |
| recipients (optional)             | Will exist only for *Recognition* type Salps.<br />An array of people being recognised, in the same format as *author*.<br />One of the elements of this array may be:<br />  \{<br />    "name": "Everyone",<br />    "email": "NoEmail"<br />}<br />which indicates that everyone in the organisation is being recognised in this Salp.                                                                                                                                          |
| content                           | The body of the Salp. eg: "Thanks to Sarah for her help yesterday in preparing the Myatt Project pitch. It really helped sell the idea!!".<br />Notes: Newlines will be represented as \n. @mentions will be flattened to the person's name.                                                                                                                                                                                                                                       |
| media (optional)                  | An array of (up to 3) media to include with the Salp. Only *image* type media are supported at this stage.                                                                                                                                                                                                                                                                                                                                                                         |
| openInSalpulse                    | A link to open the Salp directly in the Salpulse app.                                                                                                                                                                                                                                                                                                                                                                                                                              |

**Example Story Salp Display in Your Own App**

:::hint{type="success"}
**Story Salp** - Gavin Boyd wrote about our Purpose: "Shaping futures through excellence in manufacturing".

When a production issue threatened to delay a major customer order last month, teams from across the business came together to find a solution, aiming to maintain quality while meeting the deadline. A week later, the customer shared that receiving the order on time had allowed them to secure a new contract and create additional jobs within their business. It was a powerful reminder that what we manufacture is only part of our impact. Through our commitment to quality, reliability, and excellence, we help customers grow, create opportunities, and build stronger futures, bringing our purpose to life every day. [Open in Salpulse](https://app.salpulse.com)
:::

Example's construction hint: "**Story Salp** - \<author.name> wrote about our \<trimWithElipses(about.type, 120 /\*max display length\*/)>: \\"\<about.title>\\"\n\n\<content> \<appendLink()>"

**Example Recognition Salp Display in Your Own App**

:::hint{type="success"}
**Recognition Salp** - Ben Thompson recognised Sarah Wilson and Priya Sharma for the Value: *Care for each other.*

A heartfelt thank you to Sarah and Priya for the way they supported each other and the wider team during a particularly busy week. By sharing the workload, checking in regularly, and stepping up whenever help was needed, they ensured deadlines were met without anyone feeling overwhelmed. Their actions are a great example of our value "Care for Each Other", reminding us that looking out for one another makes our team stronger and more successful.  [Open in Salpulse](https://app.salpulse.com)

::Image[]{src="https://api.archbee.com/api/optimize/JIBbX60uyU8EEhh2IsyiQ/V2iegodicjl9_HugSTCHQ_salp-example-rec.jpg" size="50" isUploading="false" width="800" height="537" darkWidth="800" darkHeight="537" showCaption="false"}
:::

Example's construction hint: "**Recognition Salp** - \<author.name> recognised \<recipients\[0].name> and \<recipients\[1].name> for the \<trimWithElipses(about.type, 120 /\*max display length\*/)>: \\"\<about.title>\\"\n\n\<content> \<appendLink()>\n\n\<media\[0]>"

