Building real-estate property lead handoff with Google Forms and AI agents
A buyer fills out a property inquiry form at 11pm on a Tuesday. Within thirty seconds, an AI agent has looked up the property in the listing system, cross-referenced the buyer's email against the CRM to see if they're an existing contact, scored the lead based on the property's price point and the inquiry's wording, drafted a personalised email response with three similar listings, and assigned the lead to the right human agent based on territory and availability. By the time the buyer reads the auto-reply over breakfast, there's already a calendar invite waiting for a viewing slot the human agent has confirmed.
That's the lead handoff workflow most real-estate teams are building right now — and the part that's deceptively hard isn't the agent reasoning, it's the plumbing. The AI agent step (whether you're running it directly against an LLM, orchestrating via n8n, Inngest, or Trigger.dev) does its job well in isolation. But the events that trigger it, and the events it emits back into your stack, are the part that breaks at 11pm on a Tuesday when the on-call engineer is asleep.
This guide walks through that plumbing layer end to end — seven concrete steps to wire it up, and the production concerns that show up the moment you start handling real volume.
The flow
flowchart TB
A[Google Form<br/>submission] --> B[Apps Script<br/>trigger]
B -->|POST JSON| C[Hookdeck<br/>inbound source]
C -->|filter + transform<br/>+ rate limit| D[AI agent runtime<br/>n8n / Inngest /<br/>Trigger.dev / direct]
D -.->|enrichment| E[CRM lookup,<br/>listing lookup,<br/>scoring]
D -.->|agent decisions| F[Email reply,<br/>calendar invite,<br/>territory routing]
F -->|POST JSON| G[Hookdeck<br/>callback source]
G -->|transform + retry| H[CRM / Slack /<br/>calendar / database]
There are two webhook flows that need to be reliable:
- Form submission to the AI agent — must not drop a submission, even when your agent runtime is briefly down, redeploying, or rate-limited by an upstream LLM. A lost submission is a lost lead.
- The agent's decisions back to the systems that act on them — must reach the CRM, the calendar, and the assigned human agent's Slack reliably. If the AI agent qualifies a lead and books a viewing but the calendar webhook fails, the human agent never knows the meeting exists.
Most teams build this with a try/except around an HTTPS call and a single POST /webhooks/agent-output endpoint on their app server. That's enough to demo. The reasons it isn't enough to run are the reasons Hookdeck sits in the middle.
What you'll need
- A Google account with edit access to the form you'll use as the lead source
- An AI agent runtime — for this walkthrough we'll show both a direct-to-LLM path (e.g. Claude or OpenAI) and a workflow-engine path (n8n). The same pattern applies to Inngest, Trigger.dev, or your own service
- A Hookdeck Event Gateway account — the free tier covers this entire workflow at low volume
- Hookdeck CLI installed locally:
npm install hookdeck-cli -gorbrew install hookdeck/hookdeck/hookdeck - A destination for agent outputs — a CRM webhook URL, a Slack incoming webhook, or both
Step 1: Wire Google Forms to fire a webhook
Google Forms doesn't fire webhooks natively. The cleanest workaround is a bound Apps Script trigger.
Open your form, click the three-dot menu, choose Script editor, and paste:
const HOOKDECK_URL = 'https://hkdk.events/YOUR_SOURCE_ID';
function onFormSubmit(e) {
const responses = e.namedValues;
const payload = {
submitted_at: new Date().toISOString(),
form_id: e.source.getId(),
name: responses['Full name']?.[0] || null,
email: responses['Email']?.[0] || null,
phone: responses['Phone number']?.[0] || null,
property_id: responses['Property reference']?.[0] || null,
budget: responses['Budget range']?.[0] || null,
timeline: responses['When are you looking to buy?']?.[0] || null,
notes: responses['Anything else we should know?']?.[0] || null,
};
UrlFetchApp.fetch(HOOKDECK_URL, {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload),
muteHttpExceptions: true,
});
}
Save, then add an installable trigger: Triggers → Add Trigger → function onFormSubmit, event source From form, event type On form submit. Authorize when prompted.
Every form submission will now POST a normalized JSON payload to your Hookdeck source.
Step 2: Create the Hookdeck source
In the Hookdeck Event Gateway dashboard:
- Create Connection → New Source
- Type: HTTP
- Name:
google-forms-property-inquiries
Copy the generated URL into the HOOKDECK_URL constant in your Apps Script and submit a test entry. The payload should appear in the Hookdeck dashboard within seconds.
Step 3: Add the destination — the AI agent endpoint
Where the destination URL points depends on how you're running the agent. Two common patterns:
Direct-to-LLM path. You run a small handler (a Cloudflare Worker, a Vercel function, a Lambda) that receives the lead payload, calls Claude or OpenAI with a prompt template, processes the response, and writes back to your systems. Hookdeck delivers to that handler's URL.
Workflow engine path. You run the agent inside n8n, Inngest, or Trigger.dev as a multi-step workflow — enrichment lookups, an LLM call, conditional routing, parallel side-effects. Hookdeck delivers to the workflow's webhook trigger URL. Receiving and replaying external webhooks in n8n with Hookdeck covers this pattern in depth.
For either path, configure the destination:
- Type: HTTP
- URL: your handler URL or workflow webhook URL
- Authentication: an HTTP header carrying a shared secret or bearer token so your endpoint can verify the request came through Hookdeck
Step 4: Add filter, transformation, and rate-limit rules
In the same connection, configure:
Filter — drop submissions that aren't worth the agent's time and tokens:
{
"body": {
"email": {
"$regex": "^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$"
},
"property_id": {
"$exists": true,
"$ne": null
}
}
}
Spam, test entries, and submissions missing a property reference never burn LLM credits.
Transformation — shape the payload into whatever the agent expects. For a direct-to-LLM path, you might enrich the payload with a system prompt template:
addHandler('transform', (request, context) => {
const body = request.body;
request.body = {
lead: {
name: body.name,
email: body.email,
phone: body.phone,
property_id: body.property_id,
budget: body.budget,
timeline: body.timeline,
notes: body.notes,
submitted_at: body.submitted_at,
},
agent_instructions: {
tasks: [
'Look up property details by property_id',
'Check if email is in existing CRM contacts',
'Score lead from 1 to 5 based on budget + timeline + property price',
'Draft a personalised email reply suggesting 2-3 similar listings',
'Assign to the human agent responsible for the property territory',
'Book a tentative viewing slot if timeline is "this week" or "this month"',
],
output_schema: 'lead_handoff_v1',
},
};
return request;
});
If you're using a workflow engine, the payload can be flatter — the workflow itself owns the steps.
Rate limit — cap delivery to whatever your agent runtime can handle. For an LLM-backed agent, 2 requests per second with a burst of 5 is a comfortable starting point that respects most upstream model rate limits:
- Rate:
2per second - Burst:
5
Retry policy — your agent runtime will occasionally return 5xx (cold-start, deploy, transient LLM error). Exponential backoff handles it:
- Initial delay:
30 seconds - Max attempts:
10 - Max age:
24 hours - Apply on status codes:
408, 429, 500, 502, 503, 504
Step 5: Test the inbound leg locally with the CLI
Before pointing the destination at a real agent, route through the CLI to inspect what the agent will see.
hookdeck login
hookdeck listen 3000 google-forms-property-inquiries
Run a tiny local server:
// inspect.js
const http = require('http');
http.createServer((req, res) => {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
console.log('---');
console.log('Headers:', req.headers);
console.log('Body:', JSON.parse(body));
res.writeHead(200);
res.end('ok');
});
}).listen(3000);
node inspect.js, submit a real test entry on the form, watch the transformed payload appear in your terminal. Iterate on the transformation until the agent payload looks right — press r in the CLI terminal to replay the most recent event without re-submitting the form.
Once it looks correct, repoint the destination at your real agent runtime URL.
Step 6: Wire agent outputs back through Hookdeck
The AI agent doesn't just consume the form submission — it produces side effects: an email to the buyer, a calendar invite, a CRM contact update, a Slack ping to the territory agent. Each of those is a webhook into a different downstream system.
The most reliable pattern is to have the agent emit a single structured output webhook to a Hookdeck source, and let Hookdeck Event Gateway fan it out and route it to the right destinations. That way the agent runtime only has to deliver one webhook reliably; Hookdeck handles the rest.
Create a second connection:
- Source: a new HTTP source named
agent-lead-handoff-outputs - Destination: this is where Hookdeck's routing shines — you can create one connection per output type:
agent-lead-handoff-outputs→ CRM webhook (type = contact_update)agent-lead-handoff-outputs→ Calendar webhook (type = booking_request)agent-lead-handoff-outputs→ Slack webhook (type = agent_assignment)agent-lead-handoff-outputs→ Email service webhook (type = reply_drafted)
Each connection has a filter on the type field:
{
"body": {
"type": "contact_update"
}
}
A single agent output fires once into Hookdeck Event Gateway and fans out to every system that cares. If one downstream is briefly down (say your CRM is in the middle of a deploy) only that connection retries; the others deliver immediately.
Retry policy on these callbacks should be aggressive, because losing them means the agent's work is wasted:
- Initial delay:
15 seconds - Max attempts:
15 - Max age:
72 hours
Step 7: Run the full chain end to end
Submit a form entry with a real-looking property reference. You should see, in order:
- The submission appear in the
google-forms-property-inquiriesconnection with the transformed payload - A successful 200 response from your agent runtime
- Four events in the
agent-lead-handoff-outputsconnection (or however many side effects your agent emits) - Each event delivered to the appropriate downstream — CRM, calendar, Slack, email
- The human agent receives a Slack message; the buyer receives an email; the calendar has a tentative invite
If anything fails (a missing field, an LLM timeout, a CRM 500) the Hookdeck Event Gateway dashboard shows you exactly which event, which connection, what was sent, and what came back. No log-spelunking required.
Why Hookdeck and not just a try/except in your app server?
Three properties of AI-agent workflows make a direct integration the wrong choice once you're past the demo:
They're bursty. Real-estate leads cluster — open-house weekends, new listings, the moment a Google Ads campaign goes live. A listing that gets featured on a popular property aggregator can deliver fifty form submissions in an hour. An LLM-backed agent has both rate limits (on the model API) and concurrency limits (on its own runtime). Hookdeck Event Gateway's queueing and rate limiting smooth bursts into a sustainable stream without you writing a job queue.
Agent outputs are retry-sensitive. Each side effect the agent emits — calendar invite, email reply, Slack ping — has its own downstream that can fail independently. With a single in-process try/except, one failed CRM update can take down the entire pipeline or get swallowed silently. Hookdeck Event Gateway retries each connection independently, so a CRM outage doesn't stop the buyer from receiving their email reply.
They fail in ways that need debugging, not just retrying. Agents are non-deterministic. They occasionally hallucinate, occasionally pick the wrong tool, occasionally produce output that doesn't match the schema your downstream expects. Every one of those is a support ticket. With Hookdeck Event Gateway, the dashboard shows you the exact payload the agent emitted, the exact response the downstream returned, and lets you replay events after you've fixed the agent's prompt.
You can put all of this on your own infrastructure: a queue, a retry worker, a routing rule engine, a payload store, an observability layer, a replay tool. That's the work Hookdeck Event Gateway collapses into a connection in a dashboard. The hours you don't spend building and maintaining that infrastructure are hours you spend on the agent and the customer experience, which is where your real competitive value lives.
Going to production
Observability for the business, not just engineering. Hookdeck's Issues feature surfaces failure patterns automatically — repeated retries on the same endpoint, schema validation failures, payload spikes. Set up a Slack alert when the agent's emails start failing on a Friday afternoon, so it doesn't go undetected over the weekend.
Tune retries for the user-perceived deadline. A buyer who filled out a form expects a reply within minutes, not hours. Aggressive early retries with a short fallback path (e.g. Slack alert to a human agent after three failed agent attempts) protects the user experience even when the AI runtime is having a bad twenty minutes.
Replay deliberately when you update the prompt. Agent prompts change often in the first few months. Hookdeck Event Gateway's event replay lets you re-fire any historical event into a new version of the agent runtime — useful for testing prompt changes against real submissions before flipping the live destination over.
Handle PII responsibly. Names, emails, phone numbers, and free-text inquiry notes pass through this pipeline. Hookdeck supports payload redaction so sensitive fields don't appear in dashboards or logs. Configure it before you go live.
What to build next
If you're building any of this, the fastest way to get past the demo phase is to stop maintaining your own webhook infrastructure. Start with the Hookdeck free tier (you can run this entire workflow without paying anything until you hit real volume) and use the CLI to keep your development loop fast.