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

# API and webhooks

> Create API keys for the Triggr Public API and send signed events to your portal or PSA integration.

Only admins can manage API keys and webhooks. Endpoints, scopes and a playground are in the [API reference](https://kb.mspprocess.com/api-reference/introduction.md).

<video controls preload="none" playsInline poster="/images/videos/api-and-webhooks.jpg" src="https://mintcdn.com/triggr/arzEeC0dwWyO5_rA/images/videos/api-and-webhooks.mp4?fit=max&auto=format&n=arzEeC0dwWyO5_rA&q=85&s=addb471c44fc3abff73d5fb233766446" />

The API and webhooks: creating, rolling and revoking a key, adding a webhook, and testing it (1:23).

## Create an API key

**1. Open Settings > API**

Go to **Settings > API** and click **Create key**.

![The API keys tab with a Create key button and a table listing Client portal with core:run and executions:manage scopes and PSA sync with read + embed, both Active, each with Roll and Revoke actions.](https://mintcdn.com/triggr/MwzXHG3UWzFixdSz/images/core/api-and-webhooks/api-keys.png?fit=max\&auto=format\&n=MwzXHG3UWzFixdSz\&q=85\&s=2a8005602ae281f4acf35400a1f72f57)

**2. Name it and tick its scopes**

Every key can read your clients, forms, workflows, executions and Core requests, and embed forms as a signed-in user. Tick a scope only if the integration needs it, then click Create key.

![The Create API key dialog with Name Client portal and three scope checkboxes: Run Core forms natively core:run, ticked, Configure Core workflows core:configure, and Act on running work executions:manage.](https://mintcdn.com/triggr/MwzXHG3UWzFixdSz/images/core/api-and-webhooks/api-create.png?fit=max\&auto=format\&n=MwzXHG3UWzFixdSz\&q=85\&s=752284bcd2ad1a4ff8b11f9c72e22161)

**3. Copy the key**

Click **Copy** before Done: the key is shown once. Keys expire after a year.

![The Copy your API key dialog showing a key beginning tpk\_ with a Copy button, the note Send it as Authorization: Bearer \<key>, and a Done button.](https://mintcdn.com/triggr/MwzXHG3UWzFixdSz/images/core/api-and-webhooks/api-created.png?fit=max\&auto=format\&n=MwzXHG3UWzFixdSz\&q=85\&s=4859d715f96542a8f745499dba92205b)

| Scope | Lets the key |
| - | - |
| `core:run` | Submit Core forms from your own UI, through the same approval as Core. |
| `core:configure` | Read and change Core configuration, including each client's groups, licences and mailboxes. |
| `executions:manage` | Decide approvals, and cancel or retry executions. |

Roll issues a replacement and keeps the old key working for 1, 24 or 72 hours (or until it expires, if sooner). Revoke stops it at once and disables any webhooks registered with that key.

## Add a webhook

**1. Open Settings > Webhooks**

Go to **Settings > Webhooks** and click **Add webhook**.

![The Webhooks page with Refresh and Add webhook buttons and a table listing https://portal.yourmsp.com/hooks/triggr, Portal notifications, subscribed to execution.completed, execution.failed, approval.requested and approval.decided, Active, last delivery 13 minutes ago, registered by Core, with Test, Deliveries, Rotate secret and Delete actions.](https://mintcdn.com/triggr/MwzXHG3UWzFixdSz/images/core/api-and-webhooks/webhooks.png?fit=max\&auto=format\&n=MwzXHG3UWzFixdSz\&q=85\&s=b1b6df0fb2cf27fbb24bfaad7e769a8e)

**2. Enter the endpoint and events**

The Endpoint URL must be public `https`; redirects aren't followed. Tick the events you want, or All events to include future ones, then click Add webhook.

![The Add webhook dialog with Endpoint URL https://psa.yourmsp.com/webhooks/triggr, Description PSA integration, All events ticked, and Cancel and Add webhook buttons.](https://mintcdn.com/triggr/MwzXHG3UWzFixdSz/images/core/api-and-webhooks/webhook-add.png?fit=max\&auto=format\&n=MwzXHG3UWzFixdSz\&q=85\&s=a12f399f49428165cd47ca40ef1de7aa)

**3. Copy the signing secret**

Click **Copy** before Done: the secret is shown once.

![The Webhook added dialog showing a signing secret beginning whsec\_ with a Copy button, a note on the X-Triggr-Signature header, and a Done button.](https://mintcdn.com/triggr/MwzXHG3UWzFixdSz/images/core/api-and-webhooks/webhook-added.png?fit=max\&auto=format\&n=MwzXHG3UWzFixdSz\&q=85\&s=03fae438fbd20286553a20b2d2477dc4)

**4. Test it**

Click **Test** on the row to send a `ping`, then Deliveries to see each attempt's HTTP code and error.

![The Recent deliveries dialog for https://portal.yourmsp.com/hooks/triggr listing execution.completed delivered with HTTP 200, approval.requested pending after 2 attempts with HTTP 503 and a next attempt time, and a ping delivered with HTTP 200.](https://mintcdn.com/triggr/MwzXHG3UWzFixdSz/images/core/api-and-webhooks/webhook-deliveries.png?fit=max\&auto=format\&n=MwzXHG3UWzFixdSz\&q=85\&s=d2c380942b80b726083abf7bbe88ac0b)

| Event | Sent when |
| - | - |
| `core.request.submitted` | A Core form is submitted. |
| `execution.completed`, `execution.failed` | A run finishes or fails. |
| `approval.requested`, `approval.decided`, `approval.expired` | An approval is created, decided or expires. |
| `client.created`, `client.updated`, `client.deleted` | A client changes. |
| `core_form.updated` | A Core form's settings change. |
| `form.created`, `form.updated`, `form.deleted` | A Triggr Studio form changes. |
| `ping` | You click Test. |

Triggr POSTs JSON, normally within a minute, with `X-Triggr-Event`, `X-Triggr-Delivery-Id` and `X-Triggr-Signature` headers. `data` is the object the event is about; `id` stays the same across retries, so deduplicate on it.

```json theme={null}
{
  "id": "3f2b9c1e-…",
  "event": "ping",
  "createdAt": "2026-10-12T09:00:00Z",
  "organisationId": "org_…",
  "data": { "message": "Triggr webhook test", "at": "2026-10-12T09:00:00Z" }
}
```

### Verify the signature

`X-Triggr-Signature` is `t=<unix>,v1=<hex>`: an HMAC-SHA256 of `t + "." + rawBody` with your secret. Reject a `t` older than five minutes. For 24 hours after you click Rotate secret, the header carries two `v1` values; accept either.

```js theme={null}
const crypto = require('node:crypto')

// rawBody is the unparsed request body (a Buffer or string)
function verifyTriggrWebhook(header, rawBody, secret) {
  const fields = (header || '').split(',')
  const t = fields.find((f) => f.startsWith('t='))?.slice(2)
  const sigs = fields.filter((f) => f.startsWith('v1=')).map((f) => f.slice(3))
  if (!/^\d+$/.test(t || '') || !sigs.length) return false
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex')
  return sigs.some((v1) => /^[0-9a-f]{64}$/.test(v1) && crypto.timingSafeEqual(Buffer.from(v1, 'hex'), Buffer.from(expected, 'hex')))
}
```

### Retries

Answer 2xx within 15 seconds. Triggr retries after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours. After 25 failures in a row the webhook shows Disabled: fix the endpoint and click Enable. Deliveries keeps 30 days; Redeliver sends one again, and Delete stops deliveries and removes the history.

## Troubleshooting

> [accordion] Settings > API or Webhooks says only organisation admins can manage them
>
> Ask an [Admin](https://kb.mspprocess.com/core/get-started/invite-your-team.md#invite-teammates) to create the key or webhook, or to change your role.

> [accordion] Test fails straight away
>
> The URL must be `https`, on a public host, and answer within 15 seconds without redirecting. Private addresses are refused.

> [accordion] Signatures don't verify
>
> Compute the HMAC over the raw request body, not a re-serialised copy, and check your endpoint's clock: the sample code rejects a `t` more than five minutes out.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.