How to build a short-term rental direct booking site
with Claude, Smoobu & Stripe — in 10+ languages

Airbnb's guest-side service fee adds 14–16% on top of your nightly rate. On a €700 weekly booking, that's €100 disappearing before a guest even confirms. A direct booking site cuts platforms out of that transaction — and with Claude as your build partner, it's a realistic weekend project, not a multi-month engineering commitment.

My own Altbau flat near Boxhagener Platz in Friedrichshain runs on exactly this setup — guests book at a lower price than on Airbnb, same apartment, same hosts. This guide covers the full stack: booking engine, payment flow, multilingual SEO, analytics, an automated Claude agent loop that catches and fixes issues overnight, security hardening, and German legal compliance. Every section includes working code.

The system, drawn as a loop

The key insight is that this isn't a website you build and forget. It's an iterative system — every layer feeds the next, and each iteration makes the whole thing more reliable.

Build Claude AI Audit SEO, Security & legal skills Deploy GitHub → Vercel Bookings Smoobu · Stripe Measure PostHog · GA4 · Telegram feedback loop Pipeline step Loops back — next iteration

A guest books → GA4 and PostHog fire → a nightly smoke check runs → if something breaks, Telegram alerts → a ticket appears in the backlog → Claude picks it up → a fix deploys → the loop runs again. The core booking flow ships in a weekend. The monitoring, agent loop, and legal layers compound over the weeks that follow — that's by design.


1 Foundation: GitHub + Vercel

Set up the deployment pipeline before anything else. Every subsequent layer ships through it.

Create the repo and project

Terminal
npx create-next-app@latest your-booking-site --typescript --tailwind --app
cd your-booking-site
git init && git remote add origin git@github.com:yourorg/your-booking-site.git
git push -u origin main

Connect Vercel

  1. Go to vercel.com → New Project → Import from GitHub
  2. Select the repo — Next.js is autodetected, no config needed
  3. Every push to main now auto-deploys to production

No CI files, no build scripts, no server to manage. Vercel handles it.

Environment variables

Set these in Vercel Dashboard → Settings → Environment Variables before writing API code. Mirror locally in .env.local (gitignored — never commit secrets).

SMOOBU_API_KEY=...
SMOOBU_APARTMENT_ID=...
STRIPE_SECRET_KEY=sk_live_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
NEXT_PUBLIC_POSTHOG_KEY=phc_...
TELEGRAM_BOT_TOKEN=...
TELEGRAM_CHAT_ID=...
CRON_SECRET=...                          # random string, verify in cron handler

Scaffold with Claude

Once the repo exists, brief Claude on the full system in a single session:

Prompt to start with
"Build a Next.js 16 App Router direct booking site for a short-term rental. It needs: Smoobu availability and pricing API, Stripe PaymentIntent payment flow with webhook, multilingual routing for 12 locales, and a Telegram alerting module. Start with the folder structure and the Smoobu client."

Claude will scaffold the architecture, flag the non-obvious traps (webhook ordering, the timezone bug), and propose the folder structure before you write a line of business logic.


2 Smoobu: availability, pricing, reservations

Smoobu is the channel manager that aggregates bookings from Airbnb, Booking.com, and your direct site. Get your API key at Settings → External Integrations → API.

Checking availability and pricing

lib/smoobu.ts
const BASE = "https://login.smoobu.com/api";
const headers = {
  "Api-Key": process.env.SMOOBU_API_KEY!,
  "Content-Type": "application/json",
};

export async function getAvailability(apartmentId: string, start: string, end: string) {
  const url = `${BASE}/rates?apartments[]=${apartmentId}&start_date=${start}&end_date=${end}`;
  const res = await fetch(url, { headers, next: { revalidate: 300 } });
  const json = await res.json();
  return json.data[apartmentId] as Record<string, {
    price: number;
    available: 0 | 1;
    min_length_of_stay: number;
  }>;
}

available: 0 means blocked. Build a Set<string> of blocked date keys and disable those dates in the calendar. The response is cached for 5 minutes (revalidate: 300) — enough freshness without hammering the API.

Smoobu availability calendar — mobile view

The calendar pulls live availability from Smoobu's rates API. Greyed-out dates are blocked — either by existing bookings or minimum-stay rules.

The timezone pitfall — read before writing any date code

Smoobu returns dates as strings: "2026-07-01". If these pass through JavaScript's new Date() and .toISOString(), you get one day early — JS parses date-only strings as UTC midnight, but your guests and calendar are in local time (Berlin is UTC+2).

Never do this
new Date("2026-07-01").toISOString().split("T")[0] → returns "2026-06-30" in UTC+2. One day early. Blocks the wrong dates.
Always do this
function toLocalDateKey(d: Date): string {
  return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,"0")}-${String(d.getDate()).padStart(2,"0")}`;
}

Apply this in both your server API routes and client-side hooks. Missing it in one place produces phantom availability bugs that only appear for guests in certain timezones.

Creating a reservation

lib/smoobu.ts
export async function createReservation(p: {
  apartmentId: string; checkIn: string; checkOut: string;
  firstName: string; lastName: string; email: string;
  adults: number; price: number;
}) {
  return fetch(`${BASE}/reservations`, {
    method: "POST", headers,
    body: JSON.stringify({
      apartmentId: Number(p.apartmentId),
      channelId: 5726551,  // "Website" channel — shows as its own source in Smoobu
      arrival: p.checkIn, departure: p.checkOut,
      firstname: p.firstName, lastname: p.lastName,
      email: p.email, adults: p.adults, price: p.price,
    }),
  });
}
channelId 5726551
This is Smoobu's internal ID for "Website" bookings. Without it, direct bookings appear as mysterious manual entries in your dashboard. With it, they show as their own channel — trackable, reportable, separate from Airbnb and Booking.com.

Discount codes — the API that doesn't work

Price breakdown with discount code input — mobile view

Smoobu has a coupon feature in their UI. The API endpoints to validate codes do not work — multiple documented and undocumented endpoints return 404s or {"success":false}. The workaround: validate server-side using an env var.

DISCOUNT_CODES=SUMMER10:10,DIRECTGUEST:5  # CODE:percent pairs

Parse case-insensitively, compute the discount from live Smoobu rates. Forward the code string to Smoobu when creating the reservation for their records, but rely on nothing from their API for validation.

The price breakdown updates live — toggle dog, enter a code, and the total recalculates instantly. Discount codes are validated server-side, never against Smoobu's broken coupon API.


3 Stripe: the webhook-first pattern

The naive flow — create Smoobu reservation first, then take payment — produces ghost reservations when guests abandon mid-checkout. Those dates stay blocked for days. The correct pattern: nothing in Smoobu until money has moved.

How it flows

Guest submits form
→ Server creates Stripe PaymentIntent (booking metadata in payload)
→ Guest pays via PaymentElement — inline, no redirect
→ Stripe calls /api/webhook: payment_intent.succeeded
→ Webhook creates Smoobu reservation + sends confirmation emails
→ Telegram alert fires: "✅ Booking confirmed"
Stripe PaymentElement on the booking site — Link, card, Google Pay, Bancontact

Stripe's PaymentElement renders all available methods automatically — card, Google Pay, Link, Bancontact, EPS. No per-method configuration, no extra fees.

PaymentIntent creation

Critical: never trust a price from the browser
Always recompute totalPrice from live Smoobu rates on the server. If you accept a price from the request body, an attacker can book any stay for €0.01.
app/api/checkout/route.ts
export async function POST(req: Request) {
  const { checkIn, checkOut, guests, firstName, lastName, email, apartmentId } = await req.json();

  // Compute price server-side — recheck on every request
  const rateData = await getAvailability(apartmentId, checkIn, checkOut);
  const totalPrice = computePrice(rateData, checkIn, checkOut, Number(guests));

  const paymentIntent = await stripe.paymentIntents.create({
    amount: Math.round(totalPrice * 100),
    currency: "eur",
    metadata: { checkIn, checkOut, guests: String(guests), firstName, lastName, email, apartmentId },
  });

  return Response.json({ clientSecret: paymentIntent.client_secret });
}

Webhook handler

app/api/webhook/route.ts
export async function POST(req: Request) {
  const body = await req.text(); // raw text — NOT req.json() — required for sig verification
  const sig = req.headers.get("stripe-signature")!;

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch {
    return new Response("Signature verification failed", { status: 400 });
  }

  if (event.type === "payment_intent.succeeded") {
    const pi = event.data.object as Stripe.PaymentIntent;
    const { checkIn, checkOut, firstName, lastName, email, guests, apartmentId } = pi.metadata;

    await createReservation({ apartmentId, checkIn, checkOut, firstName, lastName, email,
      adults: Number(guests), price: pi.amount / 100 });

    // fire-and-forget — never await in a webhook handler
    void sendGuestConfirmation({ checkIn, checkOut, firstName, email, price: pi.amount / 100 });
    void sendHostNotification({ checkIn, checkOut, firstName, email });
    void sendAlert({ type: "info", message: `Booking confirmed: ${firstName}, ${checkIn}–${checkOut}` });
  }

  return new Response("ok", { status: 200 });
}

Local webhook testing

brew install stripe/stripe-cli/stripe && stripe login
stripe listen --forward-to localhost:3000/api/webhook
# Prints whsec_... — use this as STRIPE_WEBHOOK_SECRET locally
SEPA Direct Debit — don't enable it
Guests can dispute a SEPA charge up to 8 weeks after the stay. For vacation rentals — where the stay ends before the dispute window closes — this is a meaningful chargeback risk. Stick to cards (including Apple Pay and Google Pay).

4 10+ languages

Supporting multiple languages isn't about being polite to non-English speakers. It's how your SEO reaches guests who search in German, French, Japanese, or Chinese — and why the same property can appear at the top of searches in multiple markets.

中文 (Chinese)
Seumi Berlin — Chinese version
Deutsch (German)
Seumi Berlin — German version

The same page served to a Chinese-speaking vs German-speaking visitor — different headline, different meta tags, different URL slug, different search market. Same codebase.

App Router [locale] routing

app/
  [locale]/          ← en, de, fr, es, it, nl, pl, pt, tr, ja, ko, zh
    layout.tsx
    page.tsx
    book/page.tsx
  page.tsx           ← redirect to /en
lib/i18n/index.ts
export const locales = ["en","de","fr","es","it","nl","pl","pt","tr","ja","ko","zh"] as const;
export type Locale = typeof locales[number];

Typed translation files

TypeScript enforces that every language covers every key. Add a string to en.ts without updating de.ts and the build fails — you find out at compile time, not when a guest sees a blank field.

lib/i18n/types.ts — defines the shape every locale must satisfy
export type Translations = {
  meta: { title: string; description: string };
  hero: { heading: string; noFee: string; bookNow: string };
  booking: { checkIn: string; checkOut: string; guests: string };
  // every user-facing string belongs here
}

Use Claude to generate the initial translations for each locale. Review the languages you speak, run DeepL for a second pass on the rest. One full locale takes about 20 minutes with Claude doing the first draft.

hreflang — the bit that makes SEO work

app/[locale]/layout.tsx
export async function generateMetadata({ params }: { params: { locale: Locale } }) {
  const t = translations[params.locale];
  return {
    alternates: {
      languages: {
        ...Object.fromEntries(locales.map(l => [l, `https://yourdomain.com/${l}`])),
        "x-default": "https://yourdomain.com/en",
      },
    },
    title: t.meta.title,
    description: t.meta.description,
  };
}

Without hreflang, all organic traffic collapses to /en/ regardless of the visitor's language. With it, Google serves the right locale to the right market automatically — the Chinese screenshot above can rank on Baidu, the German one on Google.de.

Localize your URL slugs
The German location page should be /de/ferienwohnung-berlin, not /de/apartment-berlin. Use the vocabulary guests actually type in each market. Ask Claude to generate locale-appropriate slugs with keyword rationale for each one.

5 Analytics: three layers with different jobs

Google Analytics 4 — the booking funnel

Three events cover the full conversion funnel. Together they answer "where are guests dropping off?"

EventFires whenKey params
begin_checkoutGuest selects dates and continuescheck_in, check_out, nights
add_payment_infoGuest submits their detailsguests, locale
purchaseBooking confirmation showntransaction_id, value, currency
Consent Mode v2 is not optional in Germany
Default both analytics_storage and ad_storage to "denied" before any script loads. Update to "granted" in the cookie banner's accept handler. This must run before the GA4 script tag — use strategy="beforeInteractive".
app/layout.tsx — before any other scripts
<Script id="consent-default" strategy="beforeInteractive">{`
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('consent','default',{analytics_storage:'denied',ad_storage:'denied'});
`}</Script>

PostHog — session recording and funnel drop-off

PostHog shows you where guests abandon the booking flow — which step, which device, which locale. The funnel report surfaces conversion problems that GA4's aggregated view won't catch.

Booking conversion funnel Last 30 days · All users
Visited booking page Opened calendar Submitted guest details Reached payment step Booking confirmed 100% 847 68% 576 −32% 41% 347 −40% 28% 237 −32% 22% 186 −22%
Overall conversion: 22% · Biggest drop-off: details step (−40%). Mobile users convert at 16%.

PostHog funnel report — illustrative numbers. The details step drop-off (−40%) is a typical finding: too many required fields, or a confusing form layout on mobile.

GDPR note: never call posthog.init() on page load. Even opt_out_capturing_by_default fires a /flags/ request before consent. Use lazy init — call posthog.init() only inside the consent-granted handler, never on mount.

PostHog + Claude: close the loop automatically

PostHog pairs unusually well with Claude Code because its event data is straightforwardly readable and pasteable. After a week of real traffic, drop the funnel CSV into a Claude session — "step 2 loses 40% of guests on mobile, here's the data by locale and device" — and ask what to test first. Claude reads the numbers, identifies the highest-leverage change, and can draft the A/B variant copy in the same session.

HogQL on demand. PostHog's query language (HogQL) is close enough to standard SQL that Claude writes it fluently from a plain-language spec: "give me booking completions grouped by locale and device type, last 90 days." Paste the query into PostHog Explore, paste the result back, iterate. The whole analysis loop takes minutes.

The agent loop hook. The nightly cron job can query PostHog's Events API for the week's booking conversion rate. If it drops more than 15% from the 4-week rolling average, a structured ticket is automatically written to the backlog — locale breakdown, device split, affected step — with the data attached. Claude picks it up in the next scheduled run and proposes hypotheses without a human ever noticing the dip first. PostHog is the sensor; Claude is the responder.

Telegram — real-time operational alerts

The most immediately useful monitoring layer. A message arrives in under 5 seconds when a booking confirms, an API fails, or a webhook misfires. No dashboard to check.

lib/alert.ts
export async function sendAlert(p: { type: "info" | "warn" | "error"; message: string }) {
  const emoji = { info: "✅", warn: "⚠️", error: "🔴" }[p.type];
  await fetch(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      chat_id: process.env.TELEGRAM_CHAT_ID,
      text: `${emoji} *your-booking-site*\n${p.message}`,
      parse_mode: "Markdown",
    }),
  });
}

Always call as void sendAlert(...) — never await it inside a webhook handler. A Telegram outage should never cause your webhook to return non-200 and trigger Stripe retries.

TriggerAlert typeWhy it matters
Booking confirmedinfo ✅Immediate confirmation without opening Smoobu
Smoobu reservation failurewarn ⚠️Double-booking risk — needs immediate attention
Availability API 5xxerror 🔴Calendar may be showing wrong dates to guests
Webhook signature failureerror 🔴Misconfigured secret or probing attempt
📊
seumi-booking-bot
bot · last seen just now
📊 Seumi Berlin — Weekly digest
Week of 9–15 June 2026
✅ Bookings: 3 confirmed (+1 vs prev week)
💶 Revenue: €1,847
BOOKING FUNNEL (POSTHOG)
├ Visited /book213
├ Calendar opened147 69%
├ Details submitted88 ← worst
├ Payment reached62 70%
└ ✅ Confirmed3 · 4.8%
BUTTON CLICKS
├ "Check availability"203
├ "Continue" step 1→2147
├ "Continue" step 2→388 ⚠ −40%
└ "Pay now"62
📱 Mobile 68% · 🖥 Desktop 32%
🌍 Top locales: de (41%) · en (33%) · zh (12%)
Mon 07:03 ✓✓

Beyond instant alerts, you can extend the Telegram bot to deliver a weekly PostHog digest — funnel numbers, button click counts, and the exact drop-off step, delivered to your phone every Monday morning without opening a dashboard.

The "Continue step 2→3" row is where guests are leaving before payment — PostHog surfaces it, Telegram delivers it, Claude can fix it.


6 The Claude agent loop: errors become fixes overnight

Monitoring that only sends alerts is still reactive. The upgrade is closing the loop — alert → ticket → fix → deploy, automatically.

Nightly smoke check

A Vercel cron job at app/api/cron/smoke/route.ts runs HTTP checks against the live site. Failures trigger a Telegram alert and write a structured Markdown ticket to the backlog.

vercel.json — add one line
{ "crons": [{ "path": "/api/cron/smoke", "schedule": "0 3 * * *" }] }

Vercel injects Authorization: Bearer $CRON_SECRET — verify this header before running any checks.

The ticket format

When a check fails, the cron handler writes a structured ticket file that Claude can read and act on:

---
status: Backlog
category: claude
---
# Availability API returning 502

Detected: 2026-06-17 03:00 CET
Check: GET /api/availability → 502
Likely cause: Smoobu maintenance window or expired API key
Action: check Smoobu status, verify API key, add retry logic if recurring

The agent loop

Claude Code can be configured to run on a schedule, scan the backlog for tickets tagged category: claude, and work through them autonomously. Errors at 3am become deployed fixes by morning without human intervention. For ambiguous issues, the agent notes its reasoning and leaves the ticket for review rather than guessing.

This kanban-driven agent pattern is described in detail in The future of AI agents looks a lot like 2010.

What the test suite looks like in practice

The reference implementation ships with three layers of automated verification:

  • 122 HTTP smoke checks — every route, status code, and content string, runnable against local dev or the live URL with a single command
  • 34 product evals covering language switching (10 locales), the full booking flow, GDPR consent gating, loading UX, and locale-specific regressions — 10 of them automated via Playwright
  • Pricing unit tests — nightly rate × guests × cleaning fee × long-stay discount, exercised in isolation from the Smoobu API

The daily cron runs a subset of the smoke checks against the live site and writes a ticket on failure. Claude picks it up overnight. Nothing pages you unless the agent genuinely can't resolve it.


7 Security hardening

Before going live with anything that handles payments or personal data, run a systematic security review. Ask Claude to audit the codebase for these specific vectors:

RiskWhat to check
Price manipulationIs totalPrice computed server-side from live Smoobu rates? Never accept it from the request body.
Webhook bypassIs Stripe signature verification running before any event processing? Uses req.text(), not req.json()?
Secret exposureDoes any NEXT_PUBLIC_ variable contain a secret key? Those are bundled into the browser.
Rate limitingAre /api/checkout and /api/availability rate-limited? Unprotected endpoints can exhaust your Smoobu API quota.
Input validationAre all booking form fields validated and sanitised server-side before being passed to Smoobu?
The most common STR site vulnerability
Accepting totalPrice from the browser. An attacker sends {"totalPrice": 0.01} in the checkout POST. If your server trusts it, they book a week for a cent. Recompute from live rates on every request.

8 SEO: letting Claude find your gaps

The multilingual setup from section 4 creates the surface area — but SEO is not set-and-forget. There are three jobs worth doing with Claude before and after launch.

GEO and AI search visibility

An increasing share of travel-intent searches now surface AI-generated summaries instead of blue links. Whether your direct booking site appears in those results depends on factors traditional SEO audits miss: passage-level citability, llms.txt discoverability, and structured data completeness. Claude can audit all three in a single session — fetch each page, compare it against the current ranking signals, and generate a prioritised fix list. For a short-term rental site this audit takes roughly 20 minutes and typically surfaces 3–5 high-impact gaps.

Add an llms.txt

Crawlers used by ChatGPT, Perplexity, and Google's AI Overviews increasingly respect /llms.txt — a plain-text index that tells AI systems what your site is and what each page covers. For a direct booking site, it's two paragraphs: what the apartment is, and what each route does. Ask Claude to draft one from your existing content; it takes five minutes to deploy.

PostHog for market conversion by language

Once you have traffic in multiple languages, PostHog's HogQL makes it easy to break down your booking funnel by locale. A query that groups $pageview events by the $pathname prefix (/de/, /fr/, /ja/) and joins them against purchase events gives you a per-language conversion rate table in seconds. In practice this reveals that one or two languages drive most revenue while others get traffic but convert poorly — usually a copy or calendar UX issue that Claude can fix in a targeted session.

claude-seo — open-source SEO skill set for Claude Code

I use a custom SEO audit workflow built on top of claude-seo — a community Claude Code skill set with 25 sub-skills covering technical SEO, GEO, schema, content quality, local search, and Core Web Vitals. Each skill runs as a parallel agent against the live URL. The repo includes an install script.

⬡ github.com/AgriciDaniel/claude-seo

Run /marketing-optimize [url] after any significant content or structural change. For a multilingual STR site, prioritise the seo-geo and seo-schema sub-skills first.


Skipping the legal pages is the fastest route to an Abmahnung. Four pages, each with specific requirements under German law:

PageWhat it requires
Impressum§ 5 TMG: full name, address, phone, email, VAT ID. Berlin STR operators: also your Zweckentfremdungsverbot permit number.
AGB / TermsCancellation policy, check-in/out times, house rules, payment terms, liability limits.
WiderrufsbelehrungShort-term rentals are exempt from the 14-day consumer withdrawal right (§ 312g BGB, accommodation exception) — but you must explicitly declare the exemption. Omitting the page is not the same as declaring it.
DatenschutzerklärungEvery data processor named: Smoobu, Stripe, Google Analytics, PostHog, Vercel. Update whenever you add a new tool.
Use deutsches-recht-mit-claude to audit and fix

Rather than writing these pages from scratch or hoping a generic template is current, use the deutsches-recht-mit-claude skill set — a Claude Code integration that pulls live statute text from the official German federal law portal (rechtsinformationen.bund.de) and checks every legal page against the current wording of the relevant laws.

⬡ github.com/waldo-van-der-code/deutsches-recht-mit-claude

The skill reads each legal page, cites the current statute, flags missing disclosures, and generates corrected copy. Run it before every deploy that adds a new third-party service.

The most common gap: adding an analytics tool or payment method and forgetting to update the Datenschutzerklärung. The security review step catches this automatically.


Architecture

Four layers, each with a clear responsibility. The layers don't bleed into each other — the presentation layer never calls Smoobu directly, the API routes delegate immediately to lib/, and the server libraries know nothing about HTTP.

Architecture diagram showing four layers: Presentation (Next.js pages and components), API Routes (/api/availability, /api/checkout, /api/webhook), Server Libraries (smoobu.ts, stripe.ts, email.ts, alert.ts, analytics.ts), and External Services (Smoobu, Stripe, Gmail, Telegram, GA4, PostHog, Vercel)

The DateRangePicker is the most complex piece in the presentation layer — it holds the two-phase click model, timezone normalization, and minimum stay enforcement. /api/webhook is the critical path: Stripe fires it on payment completion, and it creates the Smoobu reservation and sends emails in a single atomic sequence. If Smoobu changes their API, the change is contained to lib/smoobu.ts.


Go live this weekend

The core booking flow ships in a weekend. Everything else is a continuous loop that compounds over the weeks that follow — which is exactly the point of drawing the system as a circle.

Weekend — ship the core

  • GitHub repo + Vercel deploy
  • Smoobu availability API + calendar
  • Stripe PaymentIntent + webhook
  • Confirmation emails wired
  • English-only launch — smoke test, go live

Ongoing — close the loop

  • Add remaining languages (one evening each)
  • GA4 + PostHog + cookie consent
  • Telegram alerts
  • Nightly smoke check + agent loop
  • Security audit + legal pages
Wire confirmation emails before Stripe
Two weeks of testing without emails makes every test booking feel like a black box. Wire email on day one so the full flow is visible from the first test.

Full stack summary

LayerToolNotes
FrameworkNext.js 16 App RouterTypeScript, Tailwind, Vercel adapter
HostingVercelFree Hobby tier is sufficient
Channel managerSmoobuAPI key: Settings → External Integrations
PaymentsStripe PaymentIntent + PaymentElementWebhook-first; Apple/Google Pay automatic
i18n[locale] dynamic segmentTyped TS translation files per locale
Analytics — funnelGoogle Analytics 4Consent Mode v2, 3 conversion events
Analytics — sessionsPostHog EU cloudLazy init for GDPR; no pre-consent requests
AlertingTelegram Bot APIFire-and-forget, 4 alert types
MonitoringVercel cron → smoke checkDaily at 03:00 → Telegram + backlog ticket
Agent automationClaude Code on schedulePicks up backlog tickets autonomously
SecurityClaude audit before each major deploy5 specific checks (see section 7)
Legal (DE)deutsches-recht-mit-claudegithub.com/waldo-van-der-code/…

Want help setting this up?

Available for consulting on STR tech stacks, Smoobu integrations, multilingual booking sites, and Claude-powered automation loops.

Get in touch →