Home Pricing Docs Blog Changelog About Log in Go to your tickets
Browse docs

Webhook Notifications

Overview

The webhook integration delivers helpdesk notifications to any public HTTPS endpoint you control. When an event you subscribed to happens (a ticket is opened, a contact replies, you are assigned a ticket, and so on), There There sends a signed JSON POST to the URL you configured. Every delivery is logged, so you can inspect the request and response and resend it if needed.

Unlike Slack or Discord, webhooks need no authorization step. You only provide a URL, so you can wire notifications into your own systems: a chatops bot, an on-call pager, a logging pipeline, or anything else that accepts an HTTP request.

Setting up a webhook destination

Webhooks are available in two places, exactly like the email, Slack, and Discord channels:

  • Settings > Notifications for personal alerts. Supports all five notification types listed below.
  • Settings > Workspace Notifications for team-wide alerts. Supports only Ticket opened and New reply on any ticket, since the other three are inherently personal.

To add a destination:

  1. Add a notification destination and pick Webhook as the channel.
  2. Enter the Webhook URL. It must resolve to a public https:// address. Loopback, private, and cloud metadata addresses are rejected. If you leave out the scheme, https:// is added for you.
  3. Pick which notification types should be delivered, then save.

You can send a test notification at any time to confirm your endpoint receives and accepts the request. You can also set up multiple webhook destinations, each pointing at a different URL and subscribed to a different set of events. Each destination has its own signing secret.

Notification types

The following notifications are available:

  • Ticket opened: Sent when a new ticket is received or an existing ticket is reopened, showing the subject, sender name, and email.
  • New reply on any ticket: Sent when a contact replies to any ticket, including a preview of the message.
  • Ticket assigned to me: Sent when a ticket is assigned to you, showing who made the assignment.
  • Mentioned in a private note: Sent when someone @mentions you in an internal note on a ticket.
  • New reply or private note on a ticket I'm assigned to: Sent when a contact replies, or a teammate leaves a private note, on a ticket you are assigned to.

Verifying the signature

Every request carries a ThereThere-Signature header. It is an HMAC SHA-256 of the raw request body, keyed with the signing secret. Each webhook destination has its own secret, shown (read only) on the destination's Settings tab. Use the Regenerate button there to roll it. Rolling the secret invalidates the signature for any receiver still using the old value.

Always compute the signature over the raw request body, before any JSON parsing.

PHP

$payload = file_get_contents('php://input');
$expected = hash_hmac('sha256', $payload, $signingSecret);

if (! hash_equals($expected, $_SERVER['HTTP_THERETHERE_SIGNATURE'] ?? '')) {
    http_response_code(403);
    exit;
}

Node.js

import crypto from 'node:crypto';

function isValid(rawBody, signature, signingSecret) {
    const expected = crypto.createHmac('sha256', signingSecret).update(rawBody).digest('hex');

    return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

Retries and the delivery log

Webhook requests have a 5 second timeout. A delivery is considered failed when your endpoint does not respond within that window or returns a non 2xx status. There There retries up to 3 times, waiting 10 seconds after the first attempt and 20 seconds after the second.

Every attempt is recorded on the webhook destination's Deliveries tab (date, event type, status, and response code), with the full payload and response available behind Details. Use Resend to deliver the same payload again on demand.

Payload reference

Every payload is a JSON object. All payloads share a common envelope:

Field Type Description
type string The event type (see below).
sent_at string ISO 8601 timestamp of when the notification was sent.
ulid string Unique id for this delivery. Stable across retries of the same delivery.

The ticket object (present on every ticket event) has this shape:

{
    "id": 123,
    "ulid": "01jab2cd3ef4gh5jk6mn7pq8r9",
    "number": 42,
    "subject": "I can't log in",
    "status": "open",
    "url": "https://there-there.app/...",
    "channel": { "id": 7, "name": "Support" },
    "contact": { "name": "Jane Doe", "email": "jane@example.com" }
}

channel is null when the ticket is not bound to a channel. The message/note object has this shape:

{
    "id": 999,
    "ulid": "01jab2cd3ef4gh5jk6mn7pq8r9",
    "created_at": "2026-06-05T10:00:00+00:00",
    "excerpt": "First 500 characters of the message, tags stripped."
}

User objects (assigned_user, assigned_by, mentioned_user, mentioned_by) have this shape:

{ "id": 5, "name": "Alex Vanderbist", "email": "alex@example.com" }

ticketOpened

Sent when a new ticket is received or an existing ticket is reopened.

{
    "type": "ticketOpened",
    "sent_at": "2026-06-05T10:00:00+00:00",
    "ulid": "01jab8z9r2pm3kc4td5ef6gh7j",
    "ticket": {
        "id": 123,
        "ulid": "01j...",
        "number": 42,
        "subject": "I can't log in",
        "status": "open",
        "url": "https://there-there.app/...",
        "channel": { "id": 7, "name": "Support" },
        "contact": { "name": "Jane Doe", "email": "jane@example.com" }
    }
}

newReply

Sent when a contact replies to any ticket.

{
    "type": "newReply",
    "sent_at": "2026-06-05T10:00:00+00:00",
    "ulid": "01jab8z9r2pm3kc4td5ef6gh7j",
    "ticket": { "id": 123, "...": "..." },
    "message": {
        "id": 999,
        "ulid": "01j...",
        "created_at": "2026-06-05T10:00:00+00:00",
        "excerpt": "Thanks, that worked."
    }
}

assignedToMe

Sent when a ticket is assigned to the destination owner.

{
    "type": "assignedToMe",
    "sent_at": "2026-06-05T10:00:00+00:00",
    "ulid": "01jab8z9r2pm3kc4td5ef6gh7j",
    "ticket": { "id": 123, "...": "..." },
    "assigned_user": { "id": 5, "name": "Alex Vanderbist", "email": "alex@example.com" },
    "assigned_by": { "id": 8, "name": "Freek Van der Herten", "email": "freek@example.com" }
}

assigned_by is null when the assignment was not performed by a specific user.

replyOnAssignedTicket

Sent when a contact replies to a ticket the destination owner is assigned to.

{
    "type": "replyOnAssignedTicket",
    "sent_at": "2026-06-05T10:00:00+00:00",
    "ulid": "01jab8z9r2pm3kc4td5ef6gh7j",
    "ticket": { "id": 123, "...": "..." },
    "message": { "id": 999, "...": "..." },
    "assigned_user": { "id": 5, "name": "Alex Vanderbist", "email": "alex@example.com" }
}

mentionedInNote

Sent when someone mentions the destination owner in a private note.

{
    "type": "mentionedInNote",
    "sent_at": "2026-06-05T10:00:00+00:00",
    "ulid": "01jab8z9r2pm3kc4td5ef6gh7j",
    "ticket": { "id": 123, "...": "..." },
    "note": { "id": 999, "...": "..." },
    "mentioned_user": { "id": 5, "name": "Alex Vanderbist", "email": "alex@example.com" },
    "mentioned_by": { "id": 8, "name": "Freek Van der Herten", "email": "freek@example.com" }
}

test

Sent when you trigger a test notification from a webhook destination.

{
    "type": "test",
    "sent_at": "2026-06-05T10:00:00+00:00",
    "ulid": "01jab8z9r2pm3kc4td5ef6gh7j",
    "message": "This is a test webhook from There There.",
    "workspace": { "id": 1, "name": "Spatie" }
}