Guide to Travtus Webhooks: Features and Best Practices
Travtus makes Adam, an AI assistant for residential property management teams. Adam talks to residents by SMS, email and web chat, turns their requests into tasks, and keeps lease records alongside. Travtus webhooks push those changes to your property management system, and they also carry Adam's replies when you send resident messages through the Travtus Messaging API.
| Aspect | Value |
|---|---|
| Method | POST, JSON body |
| Signature headers | svix-id, svix-timestamp, svix-signature (Standard Webhooks) |
| Success | Any 2xx within 15 seconds |
| Retries | Immediately, 5 s, 5 min, 30 min, 2 h, 5 h, 10 h, then 10 h again |
| Disabled endpoints | An endpoint failing for 5 days is disabled and an EndpointDisabledEvent is sent |
| Delivery | At-least-once; svix-id is stable across retries |
Below: how Travtus webhooks are requested and managed, the lease, task and messaging events, Svix verification, and the parts of Travtus's documentation that are thin.
What are Travtus webhooks?
Travtus delivers webhooks through Svix, which it calls its webhook partner. Its authentication page shows the Svix library verifying the svix-id, svix-timestamp and svix-signature headers with a whsec_ secret.
Setup starts with a person rather than a settings page: you email Travtus support with your URL and they send back the secret. After that, a Svix application portal, opened through a short-lived URL from the API, lets you add endpoints, choose event types, send test events and recover failed messages.
Travtus webhook features
| Feature | Details |
|---|---|
| Signature | Standard Webhooks HMAC-SHA256 |
| Signature header | svix-signature |
| Body | JSON |
| Event routing | Body field event_type |
| Delivery platform | Svix |
| Setup | Email support@travtus.com, or the webhook portal |
| Secret | whsec_..., one per endpoint |
| Response window | 15 seconds |
| Retry span | A little over a day, ending with two 10-hour gaps |
| Replay | Single resend, or bulk recovery from a date |
| Test events | Portal Testing tab |
Events and payloads
Every delivery has three top-level fields:
event_typeis the event name.message_idis the Svix-assigned message ID. Don't confuse it withbody.message_idon Messaging API replies, which is the original message's identifier.bodyis the lease, the task (with acreatorobject) or the AI reply.- Deduplicate on the
svix-idheader (stable across retries).
Common Event Types
| Event | Triggered When | body fields |
|---|---|---|
lease.update | A lease is created or updated | identifier, start_date, end_date, move_in_date, rent, deposit, notice_given, notice_date, move_out_date_requested |
task.create | A task is created | identifier, external_ref, title, summary, category, topic, importance, status, queue_name, due_datetime, creator, and more |
task.update | A task is updated | same as task.create |
task.delete | A task is deleted | same as task.create (status is deleted) |
sms.respond | AI response to an SMS message | message_id, conversation_id, sender_name, message_text, channel, company_id, human_handover_ind, portfolio_external_ref |
email.respond | AI response to an email message | same as sms.respond |
web_chat.respond | AI response to a web chat message | same as sms.respond |
Travtus documents these 7 events, and the list may not be complete.
Task status is one of created, in_progress, completed, deleted. Task importance is low, normal, high or very_high. For Messaging API replies channel is sms, email or website, and human_handover_ind is true when Travtus suggests handing over to a human.
Tasks also carry priority_score (0 to 1000), escalation_flag, onsite_indicator (onsite or offsite) and a queue_name from a fixed list of departments such as Facilities, Leasing and Renewals, or null. Either identifier or external_ref can be used to look a task up again. relevant_conversation can hold the transcript that led to the task, and summary has no length limit, so plan storage accordingly.
Published payload example
This is the task.update sample from the webhook samples library. Use its fields to explore the payload; it is a documentation example.
{
"body": {
"action_type": "Assistance",
"assignee_id": null,
"attachments": {
"file_A": "https://u.dev.hiadam.co/pi5fwe3"
},
"completed_datetime": null,
"creator": {
"email": "support@travtus.com",
"unit_external_ref": "P~4770692~467~2016",
"lease_external_ref": "A1234",
"first_name": "Bob",
"last_name": "James",
"phone_number": "+447549111111",
"resident_address": "10 Executive Park West NE",
"unit_identifier": "4efdccb7-10ca-47b0-811c-de2fae9f6682",
"unit_number": "2016"
},
"datetime_created": "2023-12-20T17:32:49.143000+00:00",
"datetime_updated": "2024-01-15T15:07:27.440000+00:00",
"description": "a description",
"due_datetime": "2023-12-19T12:00:00+00:00",
"external_ref": "1234567890",
"identifier": "26d2f5f9-b128-48cd-add8-303e067df958",
"importance": "very_high",
"notes": null,
"portfolio_external_ref": "P1234",
"portfolio_id": "b0234194-e61b-4a57-967f-6eaaee4d5219",
"priority_score": 500,
"queue_name": null,
"relevant_conversation": null,
"reminder_datetime": null,
"resident_id": null,
"status": "created",
"category": "Customer Support",
"summary": "a summary",
"title": "a title",
"onsite_indicator": "onsite",
"topic": "maintenance",
"user_goal": "fix_shower",
"escalation_flag": false
},
"event_type": "task.update",
"message_id": "51d155b3-496f-434e-b664-406836be35a0"
}
In this sample the unit and lease references sit inside creator, while Travtus's field table lists unit_identifier, unit_number, lease_external_ref and resident_address at the top level of body as well. Read them from either place.
Setting up Travtus webhooks
- Email support@travtus.com with the URL that should receive events, for example
https://example.com/webhooks/travtus. - Travtus sets up the webhook and replies with a signing secret that starts
whsec_. Store all of it, prefix included, asTRAVTUS_WEBHOOK_SECRET. Each endpoint has its own secret. - To add or change endpoints yourself, request a portal link with
GET https://api.travtus.com/webhook/app-portal-access/and your API bearer token. The response is{ "url": "..." }, a short-lived link to the portal. - In the portal's Endpoints section, add the URL and pick event types. With none selected, the endpoint receives every event, which is handy while testing.
- Use the Testing tab to send example events, then open each message to see the payload and attempts. Svix Play gives you a temporary URL if your service isn't ready.
To receive deliveries on your own machine, use the Hookdeck CLI:
npx hookdeck-cli listen 3000 travtus --path /webhooks/travtus
With Hookdeck. Give Travtus a Travtus source's URL as your endpoint. Travtus sets up webhooks through support@travtus.com and sends you the whsec_ secret; use it as the source's verification secret. The source checks the svix-* headers, accepts only POST and answers 200 well inside Travtus's 15 seconds. Requests that fail verification are recorded as failed and go no further.
Securing Travtus webhooks
Travtus's examples use the Svix library, which computes HMAC-SHA256 over {svix-id}.{svix-timestamp}.{raw body} with the base64-decoded secret after whsec_ and compares the result with each v1, value in svix-signature. The same check without the library:
const crypto = require('node:crypto');
function decodeBase64(value) {
if (typeof value !== 'string' || !/^[A-Za-z0-9+/]+={0,2}$/.test(value)) return null;
const bytes = Buffer.from(value, 'base64');
return bytes.length > 0 && bytes.toString('base64').replace(/=+$/, '') === value.replace(/=+$/, '')
? bytes : null;
}
function verifySignature(rawBody, headers, secret) {
if (!Buffer.isBuffer(rawBody) || typeof secret !== 'string' || !secret) return false;
const prefix = headers['svix-id'] ? 'svix' : 'webhook';
const id = headers[`${prefix}-id`];
const timestamp = headers[`${prefix}-timestamp`];
const signature = headers[`${prefix}-signature`];
if (typeof id !== 'string' || !id || typeof timestamp !== 'string' ||
!/^[0-9]+$/.test(timestamp) || typeof signature !== 'string') return false;
const key = decodeBase64(secret.replace(/^whsec_/, ''));
if (!key) return false;
const message = Buffer.concat([Buffer.from(`${id}.${timestamp}.`, 'utf8'), rawBody]);
const candidates = signature.split(/\s+/).filter((item) => item.startsWith('v1,')).map((item) => item.slice(3));
const expected = crypto.createHmac('sha256', key).update(message).digest();
return candidates.some((candidate) => {
const received = decodeBase64(candidate);
if (!received) return false;
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
});
}
Travtus asks you to verify the payload exactly as sent and to disable CSRF protection on the route. An Express handler that files tasks and passes replies on:
const express = require('express');
// Returns the parsed body when it is a JSON object, otherwise null.
function parseJsonObject(rawBody) {
try {
const value = JSON.parse(rawBody.toString('utf8'));
return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
} catch {
return null;
}
}
const app = express();
app.post('/webhooks/travtus', express.raw({ type: '*/*' }), async (req, res) => {
const secret = process.env.TRAVTUS_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500);
if (!verifySignature(req.body, req.headers, secret)) return res.sendStatus(401);
const payload = parseJsonObject(req.body);
if (!payload) return res.sendStatus(400); // signed, but not a JSON object
const { event_type, message_id, body } = payload;
if (typeof event_type !== 'string' || !message_id || !body || typeof body !== 'object') return res.sendStatus(400);
try {
if (await deliveries.seen(message_id)) return res.sendStatus(200);
switch (event_type) {
case 'task.create':
case 'task.update':
case 'task.delete':
await workOrders.sync(body.identifier, body.status, body);
break;
case 'sms.respond':
case 'email.respond':
case 'web_chat.respond':
if (body.human_handover_ind) await staff.assign(body.conversation_id);
await conversations.reply(body.conversation_id, body.channel, body.message_text);
break;
case 'lease.update':
await leases.upsert(body.identifier, body);
break;
default:
break;
}
await deliveries.record(message_id);
} catch {
return res.sendStatus(503); // not processed, so don't acknowledge
}
return res.sendStatus(200);
});
deliveries, workOrders, staff, conversations and leases stand for your own code. Travtus describes the top-level message_id as the Svix-assigned message ID, and its task page says it's there for deduplication. For anything that takes more than a few seconds, queue the event and answer first.
Travtus says the signed timestamp helps prevent replay attacks, and the Svix library rejects one more than five minutes old. Behind Hookdeck that check would fail on retries and replays, because Hookdeck keeps the original svix-timestamp and applies no tolerance of its own. The function above checks only the signature.
Make Travtus webhooks production-ready. Hookdeck Event Gateway answers Travtus within its 15-second window, retries your service when it's down, and routes task events and AI replies to different systems.
Travtus webhook limitations and pain points
Setup goes through support
The Problem: You can't create the first webhook or get its secret on your own.
Why It Happens: Travtus's setup instructions start with an email to support@travtus.com, and the secret comes back from support.
Workarounds:
- Ask for staging and production endpoints in the same request.
- Each endpoint has its own secret, so a re-created endpoint means asking for the new
whsec_value. - Once you have API access, use the portal link to add further endpoints yourself.
How Hookdeck Can Help: Point Travtus at one Hookdeck source and add or change your own destinations in Hookdeck, without going back to support.
A 15-second limit and disabling after 5 days
The Problem: Slow handlers cause retries, and a long outage disables the endpoint.
Why It Happens: Travtus counts a response after 15 seconds as a failure, and disables an endpoint that fails for 5 days.
Workarounds:
- Answer first and process from a queue.
- After an outage, re-enable the endpoint in the portal with Enable Endpoint, then use Recover Failed Messages.
How Hookdeck Can Help: Hookdeck answers Travtus straight away, so the endpoint stays healthy while your service is down, then retries delivery to you.
Lease events are thinly documented
The Problem: You can't be sure which lease events exist or what every field looks like.
Why It Happens: The lease page says events fire when a lease is created or updated but shows only a lease.update example, which is missing a comma and isn't valid JSON. No lease.create is documented, and sms.respond and email.respond have no examples of their own.
Workarounds:
- Subscribe to all events while testing, and log any
event_typeyou don't recognise. - Parse fields defensively until you've seen real deliveries.
How Hookdeck Can Help: Hookdeck stores every request in full, so you can see the real lease payloads and event names.
Two message IDs in reply events
The Problem: Deduplicating on the wrong ID drops real replies or keeps duplicates.
Why It Happens: Messaging replies have a top-level message_id (the Svix message) and a body.message_id (the resident's original message).
Workarounds:
- Deduplicate on the top-level
message_idor thesvix-idheader, and usebody.message_idonly to link the reply to its conversation.
How Hookdeck Can Help: Hookdeck's deduplication can key on the svix-id header, so retried deliveries are dropped before they reach you.
Best practices
Plan for human handover
When human_handover_ind is true, route the conversation to staff rather than only posting Adam's reply.
Sort tasks by priority
Use importance, priority_score and escalation_flag to order work, and queue_name to send it to the right team.
Keep track of endpoint health
The portal shows attempts per message. Check it after any deployment that touches the webhook route.
Make handlers idempotent
Travtus keeps retrying for more than a day, so store the message ID with each change. Our webhook idempotency guide explains how.
Conclusion
Travtus webhooks connect Adam's resident conversations to the rest of your property stack: tasks for your maintenance and leasing teams, lease changes, and AI replies to deliver. The Svix delivery is solid and well described. Setup through support, the 15-second limit and thin lease documentation are the parts to plan for.
Hookdeck Event Gateway keeps your Travtus endpoint responsive and routes each event to the right system. Sign up and send Travtus support a Travtus source URL.