Webhooks from Tellescope allow you to subscribe to updates that occur in the Tellescope app or client portal, so that you can keep your own systems in up-to-date in real-time

NodeJS examples in this document are largely pulled from one of our test scripts, which may be a useful reference for more examples or sophisticated usage

Getting Started

Subscription Configuration (API)

Webhooks only need to be configured once, but can be updated later to (un)subscribe to different models or actions. Use the Create Webhook endpoint initially and then the Update Webhook endpoint later as needed.

<aside> 💡 You will provide a URL as part of the subscription configuration process that will receive all POST Webhooks from Tellescope. You must also provide a secret (randomly generated) that can be used to verify that requests come from Tellescope.

</aside>

Initialize Webhook Configuration

const sdk = new Session({ apiKey: "API_KEY_GOES_HERE" })
await sdk.api.webhooks.configure({ url: "/YOUR_WEBHOOK_URL", secret: "YOUR_SECRET" })

<aside> 💡 Calling the configure endpoint again will replace your existing configuration with the newly provided information

</aside>

Subscribing

You specify each model (e.g. endusers, tickets) and each type of action (create, read, update) that you want to be notified about.

// NodeJS - Add subscription for created and updated endusers
const sdk = new Session({ apiKey: "API_KEY_GOES_HERE" })
await sdk.api.webhooks.update({ subscriptionUpdates: { endusers: { create: true, update: true  }} }),

Receiving Webhook (NodeJS / Express)

import express from "express"
import bodyParser from 'body-parser'
import crypto from "crypto"

import {
  WebhookRecord,
  WebhookCall,
} from "@tellescope/types-models"

const app = express()
app.use(bodyParser.urlencoded({ extended: true, limit: '25mb' }))
app.use(bodyParser.json({ limit: "25mb" }))

const sha256 = (s: string) => crypto.createHash('sha256').update(s).digest('hex')

const verify_integrity = ({ type, message, event, records, timestamp, integrity } : { 
  type: string, 
  message: string, 
  event?: CalendarEvent,
  records: WebhookRecord[], 
  timestamp: string, 
  integrity: string,
}) => (
  sha256(
    type === "automation" 
      ? message + timestamp + TEST_SECRET
      : type === 'calendar_event_reminder'
          ? event?.id + timestamp + TEST_SECRET 
          : records.map(r => r.id).join('') + timestamp + TEST_SECRET
  ) === integrity
)

const handledEvents: WebhookCall[] = []
app.post("/YOUR_WEBHOOK_URL", (req, res) => {
  const body = req.body as WebhookCall
  // console.log('got hook', body.records, body.timestamp, body.integrity)

  if (!verify_integrity(body)) {
    console.error("Integrity check failed for request", JSON.stringify(body, null, 2))
    process.exit(1)
  }

  handledEvents.push(req.body)
  res.status(204).end()
})

Webhook Shape

The WebhookCall type represents the JSON posted to your API endpoint

type WebhookRecord = {
  id: string,
  [index: string]: any,
}
type WebhookUpdates = {
  recordBeforeUpdate: WebhookRecord, 
  update: Partial<WebhookRecord>,
}[]
interface WebhookCall {  
  model: string,
	description?: string,
  message?: string,
  type: "create" | "update" | "delete",
  event?: CalendarEvent & { id: string },
  records: WebhookRecord[],
  updates?: WebhookUpdates,
  relatedRecords: { [index: string]: WebhookRecord },
  timestamp: string,
  integrity: string,
}

Examples

Enduser created

{  
  model: "endusers",
  type: "create"
  records: [  // includes multiple endusers on bulk create
		{ id: string, /* other enduser fields here */ }
	],
  timestamp: string,
  integrity: string,
}