Skip to main content

Webhooks

Verifying deliveries​

Deliveries for a plain URL webhook (one with no target) carry a Terros-Signature header, so you can check that a request came from Terros and was not altered. Webhooks with no signing secret, including those added before signing existed, are not signed. Webhooks with a built-in target (CONNECT, ENERFLO, SERVICETITAN, SALESFORCE or JOBNIMBUS) are never signed.

Terros-Signature: t=1700000000,v1=38877139021993b830af32feea6e18a8da83eb2f6e49ee50bd9e4cf4ca4d3789
  • t is the Unix time, in seconds, when the delivery was signed.
  • v1 is the hex HMAC-SHA256 of <t>.<raw request body>, keyed with the webhook's signing secret.

Retries and redrives are signed again, so each attempt has a fresh t.

To verify a delivery:

  1. Read the raw request body, before any JSON parsing.
  2. Compute the HMAC-SHA256 of <t>.<raw body> with your signing secret.
  3. Compare it to v1 in constant time.
  4. Reject the request if t is more than a few minutes old, to stop replays.
import { createHmac, timingSafeEqual } from 'node:crypto'

function isFromTerros(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(',').map((part) => part.split('=')))
const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex')
const sent = Buffer.from(parts.v1 ?? '')
const isSame = sent.length === expected.length && timingSafeEqual(sent, Buffer.from(expected))
return isSame && Math.abs(Date.now() / 1000 - Number(parts.t)) <= toleranceSeconds
}

Signing secret​

You choose each webhook's signing secret: a string of 16 to 256 characters, such as a long random value from a password manager. The secret is write-only. Terros stores it to sign deliveries and never returns it, so keep your own copy to verify with.

  • Send signingSecret to /webhook/add to sign a new webhook. Without one, the webhook is not signed.
  • Send signingSecret to /webhook/update to set or replace the secret, which is also how to start signing a webhook added without one. Omit it to keep the current secret.
  • In the Terros web app, enter the secret in the Signing secret field when you add or edit a plain URL webhook. Leave it blank when editing to keep the current secret. The field never shows the stored value.
  • After you replace a secret, deliveries are signed with the new one as soon as the change takes effect, which can take up to 5 minutes, so accept both secrets during the switch.