Skip to main content
Webhooks are HTTP POST requests that Timely.ai automatically sends to the URL you configure whenever a relevant event occurs in your workspace. Instead of polling the API, you receive data instantly — no latency, no wasted requests.

Why use webhooks

With webhooks you can:
  • Sync your CRM every time a new contact comes in or a conversation changes status
  • Trigger external automations (Make, n8n, Zapier, your own backend) in response to events
  • Feed BI dashboards with real-time data
  • Integrate your own support systems by receiving messages as soon as they arrive
  • Monitor channel health when a connection drops or encounters an error

How it works

1

You register an endpoint

Create a webhook in the dashboard or via API by providing your server URL and selecting which event categories you want to receive.
2

An event occurs

A contact sends a message, a conversation is closed, an appointment is created — anything you subscribed to.
3

Timely.ai sends a POST

We send a POST with the event’s JSON payload to your URL, including the X-Timely-Signature header so you can validate authenticity.
4

You respond with 2xx

Your server must respond with any 2xx status within 10 seconds. If it doesn’t respond, we begin retrying.

Development vs Production

In dev, use a tool like ngrok or Hookdeck to expose your local server over HTTPS:
ngrok http 3000
# Forwarding: https://abc123.ngrok.io -> localhost:3000
Set https://abc123.ngrok.io/webhooks/timely as the URL in the dashboard. Remember to update it every time you restart ngrok.
The POST /v1/webhooks/{id}/test endpoint sends a test event without needing to generate a real one in the workspace.

Security: HMAC signature validation

Every delivery includes the X-Timely-Signature header in the format sha256=HEX. This value is the HMAC-SHA256 of the request body using the secret generated when the webhook was created. Never skip validation. Any server on the internet could post fake data to your URL if you don’t verify the signature.
import crypto from "crypto";
import { Request, Response } from "express";

const WEBHOOK_SECRET = process.env.TIMELY_WEBHOOK_SECRET!;

export function verifySignature(req: Request, res: Response, next: Function) {
  const signature = req.headers["x-timely-signature"] as string;
  const rawBody = (req as any).rawBody as Buffer; // body-parser com verify

  if (!signature || !rawBody) {
    return res.status(401).json({ error: "Missing signature" });
  }

  const expected = "sha256=" + crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(rawBody)
    .digest("hex");

  const isValid = crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );

  if (!isValid) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  next();
}
Always use crypto.timingSafeEqual (or its equivalent in your language) to compare signatures. Direct comparison with == is vulnerable to timing attacks.

Automatic retries

If your URL does not respond with 2xx within 10 seconds, Timely.ai automatically retries with exponential backoff:
AttemptWait before retry
1st failure5 seconds
2nd failure15 seconds
3rd failure60 seconds
4th failure5 minutes
5th failure15 minutes
After 5 consecutive failures, the webhook is marked as failing and you receive an alert email. The webhook remains active — new events continue to be queued — but you need to check the endpoint before more deliveries fail.
Check the delivery history at GET /v1/webhooks/{id}/deliveries to see the status of each attempt, the HTTP code returned, and the response time.

How to create a webhook

  1. Go to Settings → Webhooks in the sidebar
  2. Click New webhook
  3. Enter the destination URL and select the events you want to receive
  4. Copy the generated secret — it is shown only once
  5. Save and use the Test button to validate delivery

Best practices

Respond fast

Return 200 OK immediately and process the event asynchronously (queue, worker, task). Never make database calls or third-party API calls before responding.

Idempotency via event_id

Every payload includes a unique event_id field. Store processed IDs and ignore duplicates — retries may re-deliver the same event.

Secret in a vault

Never expose the secret in unencrypted environment variables in CI, logs, or source code. Use a dedicated secret manager.

Monitor failures

Set up alerts on your end as well. Use GET /v1/webhooks/{id}/deliveries periodically or integrate with your observability system.

Event categories

See the detailed documentation for each event category:

Conversations

conversation.created, .assigned, .closed, .reopened

Messages

message.received, .sent, .failed, .delivered, .read

Contacts

contact.created, .updated, .deleted

Agent

agent.handoff_started, .handoff_completed, .transferred

Appointments

appointment.scheduled, .rescheduled, .canceled, .reminder_sent

Channels

channel.connected, .disconnected, .error

Billing

credit.low, .depleted, plan.changed, trial.expiring, .expired