Agent skill

Travtus Webhooks Skill

Receive and verify Travtus webhooks (the "Adam" AI assistant platform for residential property management, delivered via Svix). Use when setting up Travtus webhook handlers, debugging svix-signature verification, or handling lease.update, task.create, task.update, task.delete, sms.respond, email.respond and web_chat.respond events.

Install this skill

npx skills add hookdeck/webhook-skills --skill travtus-webhooks


Travtus (travtus.com) delivers webhooks through Svix, signed following Standard Webhooks with the svix-* header names. Each delivery is an HTTP POST with a signed JSON body and three headers: svix-id, svix-timestamp, and svix-signature. The body is an envelope: { "event_type": "task.update", "message_id": "51d155b3-...", "body": { ... } }. message_id is the Svix message id and body is the lease, the task (with a creator object) or the AI reply. No header carries the event type.

When to Use This Skill

  • How do I receive Travtus webhooks?
  • How do I verify Travtus webhook signatures (svix-signature, whsec_ secret)?
  • How do I handle lease.update, task.create, task.update or task.delete events?
  • How do I handle sms.respond, email.respond or web_chat.respond AI replies from the Messaging API?
  • Why is my Travtus webhook signature verification failing, or rejecting Hookdeck retries?

Verification (core)

Signed content is {svix-id}.{svix-timestamp}.{raw body}. Svix signs it with HMAC-SHA256 using the base64-decoded secret (the part after whsec_), base64-encodes the result, and sends one or more space-separated v1,<signature> entries. Use the official svix library, which compares in constant time. Read the svix-* headers, and when svix-id is absent fall back to the webhook-* headers (webhook-id, webhook-timestamp, webhook-signature), as Hookdeck does.

import { Webhook } from 'svix'; // svix 2.x is ESM-only

const prefix = req.headers['svix-id'] ? 'svix' : 'webhook';
const id = req.headers[`${prefix}-id`];
const timestamp = req.headers[`${prefix}-timestamp`];
const signature = req.headers[`${prefix}-signature`];
// rawBody: the raw Buffer/string, never re-serialized JSON
new Webhook(process.env.TRAVTUS_WEBHOOK_SECRET).verify(rawBody, {
  'svix-id': id, 'svix-timestamp': timestamp, 'svix-signature': signature,
  'webhook-id': id, 'webhook-timestamp': timestamp, 'webhook-signature': signature,
}); // throws on a bad signature or a timestamp outside the window
const event = JSON.parse(rawBody.toString('utf8')); // parse only after verify passes
from svix.webhooks import Webhook, WebhookVerificationError

# svix-* headers, falling back to webhook-* as Hookdeck does
prefix = "svix" if headers.get("svix-id") else "webhook"
id, ts, sig = (headers.get(f"{prefix}-{n}") for n in ("id", "timestamp", "signature"))

wh = Webhook(os.environ["TRAVTUS_WEBHOOK_SECRET"])  # "whsec_..."
wh.verify(raw_body, {"svix-id": id, "svix-timestamp": ts, "svix-signature": sig,
                     "webhook-id": id, "webhook-timestamp": ts, "webhook-signature": sig})

For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.

Travtus support sets the webhook up (email support@travtus.com with your URL) and issues a whsec_... secret, unique per endpoint. Hookdeck's configuration alert for this source reads: "Travtus sets up webhooks through support@travtus.com and sends you the whsec_ secret. Paste it here." Keep the full value in TRAVTUS_WEBHOOK_SECRET. Handlers fail closed: with the variable unset (or not a whsec_ value) they return 500 instead of accepting anything. Missing headers or a bad signature return 400. The svix library reads either the svix-* or webhook-* header names, so no mapping is needed beyond the fallback shown.

Hookdeck retries and replays hit the 5-minute window

The svix and standardwebhooks libraries always enforce a fixed 5-minute timestamp window, and it can't be configured. Hookdeck retries and replays resend the original svix-timestamp, so the library rejects any retry or replay that happens more than 5 minutes after the original delivery. If you verify behind Hookdeck (or replay old events), use the manual verifier in references/verification.md, where the window can be dropped or widened. Deduplicate on svix-id instead of relying on the timestamp.

Common Event Types

7 events are documented (the list may not be exhaustive). The name is event_type in the JSON body, and the examples handle all of them:

EventTriggered When
lease.updateA lease is created or updated
task.createA task is created (body.creator describes the originator)
task.updateA task is updated
task.deleteA task is deleted
sms.respondAI response to an SMS message sent through the Messaging API
email.respondAI response to an email message
web_chat.respondAI response to a web chat message

See references/overview.md for payload fields and delivery details (2xx within 15 s; retries immediately, 5 s, 5 min, 30 min, 2 h, 5 h, 10 h, 10 h).

Environment Variables

# Signing secret of the webhook endpoint (Svix format, starts with whsec_)
TRAVTUS_WEBHOOK_SECRET=whsec_xxxxx

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 travtus --path /webhooks/travtus

Give the tunnel URL to support@travtus.com (or add it in the webhook portal), then use the portal's Testing tab to send an example event.

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 13, 2026

View on GitHub →