Webhooks

Receive events from Twentybaan in real time.

Events

  • contact_message.created
  • listing.published
  • listing.updated

Note: webhook.test exists internally and cannot be subscribed to through the public subscribe endpoints.

Signing and delivery

Webhook deliveries are HTTP POST requests with JSON bodies and these headers: X-TB-Event, X-TB-Delivery, X-TB-Timestamp, X-TB-Signature.

Signing secret

When you create a webhook endpoint in Settings → Integrations, you get a signing secret. Use it to verify the signature.

Verify signature

# Verify against the raw request body (before JSON parsing).
secret="$TWENTYBAAN_WEBHOOK_SECRET"
timestamp="$X_TB_TIMESTAMP"
provided="${X_TB_SIGNATURE#sha256=}"
raw="$(cat webhook-body.json)"

expected="$(printf '%s.%s' "$timestamp" "$raw" | openssl dgst -sha256 -hmac "$secret" | awk '{print $2}')"

[ "$expected" = "$provided" ] || echo "Invalid signature"

Failed deliveries are retried with backoff, starting at 1 minute and increasing up to 24 hours. Timeouts are short, so respond quickly and do heavy work asynchronously.

Payload examples

Webhooks use a stable envelope: event, delivery_id, timestamp, and data. The inner data depends on the event.

contact_message.created

{
  "event": "contact_message.created",
  "delivery_id": "b5b55f1f8c0b4d45a1b28b0b6ab77d2c",
  "timestamp": "2026-01-12T07:00:00Z",
  "data": {
    "id": 456,
    "topic": "Schedule a viewing",
    "request_kind": "viewing",
    "request_status": "new",
    "request_timezone": "Asia/Bangkok",
    "spot_name": "Modern condo near BTS",
    "spot_slug": "modern-condo-near-bts",
    "from_url": "https://twentybaan.com/listings/?id=123",
    "listing_id": 123,
    "thread_id": 789,
    "name": "Jane Doe",
    "email": "jane@example.com",
    "phone": "+66 800 000 000",
    "move_in": "2026-02-01",
    "budget": "฿35,000 / month",
    "lease_length": "12",
    "occupants": 2,
    "has_pets": false,
    "notes": "Can we tour this weekend?",
    "preferred_slots": [
      {"day": "Saturday", "window": "10:00-12:00"},
      {"day": "Sunday", "window": "14:00-16:00"}
    ],
    "rep_name": "Your Agent",
    "rep_brand": "Twentybaan",
    "rep_user_id": 123,
    "created_at": "2026-01-12T07:00:00Z"
  }
}

listing.published

{
  "event": "listing.published",
  "delivery_id": "f0a1e9d2c9c24a91b5a6f0a8d8c2e3aa",
  "timestamp": "2026-01-12T07:00:00Z",
  "data": {
    "listing": {
      "id": 123,
      "status": "published",
      "slug": "modern-condo-sukhumvit",
      "title": "Modern condo near BTS",
      "city": "Bangkok",
      "area": "Sukhumvit",
      "property_type": "condo",
      "offer_type": "rent",
      "rent_price_amount": 35000,
      "rent_price_period": "month",
      "sale_price_amount": null,
      "price_text": "฿35,000 / month",
      "beds_text": "2",
      "area_sqm": 62,
      "image_url": "https://...",
      "published_at": "2026-01-12T06:59:00Z",
      "created_at": "2025-12-15T09:12:00Z",
      "updated_at": "2026-01-12T06:59:00Z"
    }
  }
}
tb twentybaan

Sign in to your account

Not a member? Create an account

Or continue with email

Uncheck on shared devices.