Connecting an External Context Webhook
Why You'd Want This
Guest Context already lets your systems push what they know about a guest to TrustYou Agent ahead of a Conversation: their name, their email, their booking, their room. The TrustYou CDP is usually the system doing that pushing, and a PMS or booking engine can do it just as well. That covers the guest data the Agent product already models, and for a lot of properties it's enough.
An External Context Webhook is for everything it doesn't cover.
The difference isn't freshness, it's ownership. With the push integration you fill in a set of fields the Agent product defines, and the product decides how the Agent is told to use each one. With a webhook, you define the fields and you write the instructions that go with them. Anything you can put behind an HTTPS endpoint becomes something the Agent can ask for, in your vocabulary, with your rules attached. That's true whether the endpoint belongs to the TrustYou CDP, your PMS, or a service your own team wrote.
That opens up three kinds of thing the push integration can't do.
Data that isn't part of the guest model. Loyalty tier and point balance. Open invoices. Whether the spa slot they're asking about is free. An order, a ticket, a membership, a shuttle booking. If one of your systems knows it and it helps the guest, you can hand it over. It doesn't have to be about a reservation, and it doesn't have to fit a shape TrustYou anticipated.
Logic you run at the moment of asking. This is the one people underestimate. Your endpoint isn't restricted to looking up stored values. It sees the guest's identifiers and can compute an answer right then, so you can resolve an intent on your side and hand the Agent a decision instead of raw data. Rather than returning a rate code, a booking channel, and a cancellation deadline, and hoping the Agent reasons its way to the right outcome, you return "for this guest, cancellations go to self-service, and tell them the amount paid is not refundable." Your rules stay in your system, where you can test them, version them, and change them without touching the Agent.
Anything too volatile or too expensive to send in advance. A push happens once, before the Conversation. Availability, queue lengths, balances, and statuses that move during a stay are better fetched at the moment the guest asks.
The payoff, whichever of those you're after:
- Answer questions your Knowledge Base structurally cannot. General property information can't tell a guest what's true for them specifically.
- Stop asking guests what you already know. No more "could you confirm whether your rate is flexible?"
- Keep your business logic in your system. The Agent doesn't reimplement your rules. It asks, and does what you say.
- Extend the Agent without waiting on us. A new endpoint and a new schema, and the Agent has a new capability.
- Fail safely. If your endpoint is slow or down, the Conversation carries on without the data instead of breaking.
A worked example runs through the rest of this article: a booking-intent endpoint that tells the Agent where a guest stands and where to route a cancellation. It's one illustration of the pattern, not the limit of it.
Note: The webhook is the pull half of guest context, and the push half still matters just as much. That's where the TrustYou CDP comes in: it sends the guest's context and identifiers ahead of the Conversation through the Integration API (see Integrating TrustYou Agent Web Chat), which is what lets the Agent open with a greeting that already knows who the guest is and what they booked. The Agent then passes those same identifiers to your webhook whenever it calls. No identifiers, no lookup.
How the Lookup Works
Before you configure anything, it helps to know the shape of what you're building.
-
The guest is identified. The chat widget passes
guestContextIdentifiers, and TrustYou Agent matches them to a conversation context. This step is a prerequisite: if the Agent doesn't know who it's talking to, it is never offered the lookup at all. - The Agent decides to look up. The webhook is exposed to the Agent as a tool. Nobody triggers it on a keyword, and it doesn't fire automatically at the start of a Conversation. The Agent reads the tool's description and judges whether the guest's question needs it. This is why your field descriptions matter so much. More on that below.
- The Agent tells the guest to hold on. The lookup runs in the background, so the Agent's reply on that turn is a short "let me check that for you" note. It does not answer yet.
- TrustYou Agent calls your endpoint and filters the response through your schema.
- The Agent answers on the next turn, using what came back.
That two-turn rhythm is normal and expected. Don't read the holding message as a bug.
Note: The Agent reuses a recent lookup rather than calling your endpoint again. By default a result stays fresh for five minutes, so a follow-up question in the same Conversation is answered from the snapshot already fetched.
What Your Endpoint Has to Do
What we send. A POST with Accept: application/json and your configured auth header. The body always matches this schema:
{
"type": "object",
"required": ["conversation_context_id", "external_identifiers"],
"properties": {
"conversation_context_id": { "type": "string" },
"external_identifiers": {
"type": "object",
"additionalProperties": { "type": "string" }
}
}
}
So a real request looks like this:
{
"conversation_context_id": "acx_9m4e2mr0ui3e8a215n4g",
"external_identifiers": {
"reservation_id": "RES-100482",
"confirmation_number": "8891042"
}
}
The external_identifiers are the same key/value pairs your upstream system pushed with the guest's context.
What we expect back. A 200 carrying a JSON object. The shape is yours to choose:
{
"type": "object",
"additionalProperties": true
}
You then declare, in the connector's response schema, which of those fields the Agent should read and what each one means. That step is covered in Step 2, and it's where most of the work is.
Your endpoint must:
- Be reachable over HTTPS. Plain HTTP is rejected.
- Return JSON, as a single flat-ish object. See the schema rules below.
-
Answer an empty-identifier probe without a server error. When you save the connector, TrustYou Agent fires a verification request with
external_identifiersset to{}. A2xx,404,400,422, or429all pass, because they all prove the endpoint is alive and your credentials work. A401,403,5xx, redirect, or405fails and puts the connector into an Error state. If your endpoint currently crashes on an empty lookup, fix that first. - Answer quickly. You set the timeout on the connector, and the default is 10 seconds. Aim comfortably below whatever you set. See Stay Inside the Timeout below.
The next section covers the rest of what a solid implementation needs. If you're handing this to a developer, that's the part to send them.
Implementing the Endpoint
This section is for whoever builds the endpoint. If you're only configuring the connector, skip to Step 1.
Log the Conversation Context ID
Every request carries a conversation_context_id, like acx_9m4e2mr0ui3e8a215n4g. It identifies the guest's Conversation context, and it stays the same for every call made on behalf of that guest session.
Put it on every log line you write for the request. It is the one value that exists on both sides of the integration, so it's what turns "a guest complained about something the Agent said last Tuesday" into a specific request in your logs. Without it you're matching on timestamps and guessing.
This matters more here than in a typical integration, because the backoffice does not show you webhook calls for a given Conversation. There's no request log, no response body, and no latency figure in the product today. Your logs are the only record of what actually happened. Log at least:
- The
conversation_context_id - The
external_identifiersyou received - Whether you found a match
- The status code and body you returned
- How long you took
Treat the identifiers as personal data and apply your usual retention rules.
Return Exactly 200
Only a 200 is read as data, and the body must be a JSON object. This catches people out, so here's the full mapping:
| What you return | What the Agent does |
|---|---|
200 with a JSON object |
Filters it through the schema and uses it. |
200 with a non-object or non-JSON body |
Treated as a schema mismatch. No data. |
404 |
Treated as "no guest matched". A normal, expected result. |
401 or 403
|
Authentication failure. No retry. |
Any other 4xx
|
Treated as a contract problem. No retry. |
5xx |
Server error. Retried, if you allowed retries. |
Any other 2xx, such as 202 or 204
|
Also treated as a server error, and retried. |
A 3xx redirect |
Not followed. Treated as an error. |
So return 200 with your payload when you have data, and 404 when the identifiers match nothing. Don't use 204 for "nothing found", and don't rely on redirects.
Remember the verification probe described above: when the connector is saved, it fires a request with external_identifiers set to {}. Answer it with a 200, 404, 400, 422, or 429. A 5xx there fails verification and disables the connector.
Match Identifiers Exactly
The identifier keys are the ones your own systems pushed with the guest's context, so you define the vocabulary. Two rules:
- Match on every pair you receive, not just the first one you recognize.
-
Compare values exactly. They're case- and format-sensitive strings.
RES-100482andres-100482are different.
If nothing matches, return 404. If more than one guest matches, treat it as no match rather than picking one.
Stay Inside the Timeout
The timeout is whatever you configured on the connector. Retries share that same deadline, so three retries on a 10-second timeout still means the whole thing ends at 10 seconds.
Treat the configurable range as headroom, not as a target. It's deliberately generous so an occasional slow lookup isn't cut off, and we tune it over time to keep replies quick as guest conversation volume grows. Build for the low end and you'll never be affected by that tuning.
The reason is simple: a guest is waiting, and the Agent has already told them to hold on. Returning a smaller answer quickly beats returning a complete one slowly. If part of your payload depends on a slow downstream call, consider leaving that field out rather than making every lookup wait for it.
Make It a Safe, Authenticated Read
-
No side effects. The same request can arrive more than once, because a timeout or a
5xxtriggers a retry, and the same guest may be looked up again in a later Conversation. Keep it a pure read. - Authenticate every request. We send the credential you configured on every call, in the header you chose. Nothing else identifies us, and there's no request signature, so that credential is your only check. Reject anything without it.
-
Expect no custom headers. Beyond
Accept: application/jsonand your auth header, there's nothing to key off.
Get the Types Exactly Right
A field whose value doesn't match its declared type is dropped silently, so type discipline on your side is what keeps data flowing:
- A
numberfield must be a JSON number.3, not"3". - A
booleanfield must betrueorfalse. Not"true", and not1. - An
enumvalue must appear in the declared list, matched exactly, including case. -
nullbehaves like a missing field. It gets dropped. If you have no value, just leave the key out. - A field marked as a List must be a JSON array. An empty array is fine and is a good way to say "nothing applies here".
Your field keys and enum values are a contract with the schema. If you rename a key or add a new enum value, the schema has to change too, or the new data quietly stops arriving. Coordinate those changes rather than shipping them independently.
Treat Everything You Return as Guest-Visible
Whatever survives the schema goes into the Agent's prompt and can shape what it says to the guest. So:
- Don't return anything the guest shouldn't see. Internal notes, staff comments, pricing logic, other guests' data. Assume it can be spoken aloud.
- Be careful with free text from untrusted sources. Text originally written by a guest or a third party becomes part of the prompt, which is a route for someone to try to influence the Agent. Prefer enums and structured values over pass-through prose.
- Send decisions, not debug output. A resolved action your system already worked out is worth far more to the Agent than the raw inputs behind it.
Step 1: Create the Connector
Connectors live at the Organization level, so one connector can serve several Agents.
- Go to Connectors in the backoffice and select Add connector.
- Pick External Context Webhook from the Context category.
- Fill in the form:
| Field | What to enter |
|---|---|
| Name | A name that describes the system, not the integration. The Agent sees this name in the tool description, so "Booking System" beats "Webhook 1". |
| Endpoint URL | Your HTTPS endpoint. The help text reads "HTTPS only. The agent POSTs the guest's identifiers here." |
| Authentication | Bearer token, Basic auth, or API key. There's no OAuth option. |
| Token / Username and Password / API key | The secret itself. For API key you also set a Header name (defaults to X-API-KEY) and an optional Value prefix, and the form previews the header it will send. |
| Timeout (seconds) | 1 to 30, default 10. |
| Max retries | 0 to 3, default 0. Retries only happen on a timeout or a server error, and they still have to finish inside the timeout window. |
- Save. The verification probe runs immediately. If it fails you'll see Verification failed, with the note that you should edit the connector and re-save to retry.
Note: Secrets are never shown again after you save. When you edit the connector later, leave the secret field blank to keep the stored value.
Step 2: Build the Response Schema
This is the part that takes the most iteration, so it gets the most space.
On the connector's detail page you'll find a Response schema card: "Define the fields the agent reads from the webhook response. Anything not declared here is dropped."
Read that last sentence twice. It is the single most important rule on this page.
It Is Not JSON Schema
If you've written tool definitions for an LLM before, set that mental model aside. Two things are different here.
First, there are no input parameters. The Agent doesn't fill in arguments. It calls the tool with nothing, and TrustYou Agent builds the request from the guest's identifiers. You are not describing what goes in. You are describing what comes back.
Second, the schema is a deliberately small subset, not the JSON Schema spec. Here's the whole of it:
| What you get | Details |
|---|---|
| Field (a leaf) | A Field key, a type, a Description, and optional Required and List flags. |
| Types |
string, number, boolean, enum. That's all four. |
| Enum | Must declare Allowed values. Non-enum fields must not. |
| List | Marks a field as an array of its type. |
| Group | One named object holding a flat list of Fields, with its own description. |
| Nesting | Exactly one level. A Group holds Fields. A Group cannot hold another Group. |
| Size limit | 20 Fields total, counting Fields inside Groups. |
| Description limit | 200 characters each. |
What you don't get: oneOf, anyOf, allOf, $ref, additionalProperties, format, minimum, maximum, default, or any nesting past the first level. None of those keywords exist here.
The caps are real budgets, not suggestions. Twenty Fields and 200 characters each is roughly 4,000 characters of description reaching the Agent. Spend it on the fields that change an answer, and leave out the ones that are merely interesting.
Anything You Don't Declare Is Silently Dropped
Your endpoint can return whatever it likes. Only declared, correctly typed fields survive:
- An undeclared key is dropped. No error, no warning in the Conversation.
-
A declared field whose value is the wrong type is also dropped. A
numberfield that arrives as"3"(a string) simply vanishes. - A Group only keeps the Fields you declared inside it. Extra keys in the group go the same way.
This is the number one cause of "the Agent ignores my data." Nothing is broken and nothing errors out. The field just never reaches the Agent, so the Agent behaves as if your system never sent it. A field you forgot to declare on one group but remembered on another produces exactly the kind of bug that takes a week to spot: the Agent mentions a no-refund rate on cancellations but never on modifications, and everything else looks fine.
The Test request tool exists to make this visible. Use it before you go live, not after.
Note: The Required flag is currently descriptive. A missing field does not fail validation, so don't rely on Required to enforce anything.
A Worked Example
A booking-intent endpoint that answers "where does this guest stand, and where should I send them?" might return this:
{
"stage_of_booking": "pre_arrival",
"refund_policy": "no_refund",
"days_until_arrival": 3,
"cancellation": {
"action": "self_service",
"inform_no_refund": true,
"self_service_url": "https://booking.example.com/manage"
}
}
That's three top-level Fields plus one Group of three, so six of your 20 Fields. In the schema builder you'd create:
-
stage_of_booking, type enum, allowed valuesbooked,pre_arrival,checked_in,checked_out,cancelled -
refund_policy, type enum, allowed valuesflexible,prepaid_flexible,no_refund -
days_until_arrival, type number -
cancellation, a Group, holdingaction(enum, allowed valuesself_service,reception,handover_to_human,inform),inform_no_refund(boolean), andself_service_url(string)
Note: The screenshot below shows this schema mid-build, with days_until_arrival and self_service_url not yet added. That's deliberate. It's the state the testing section uses to show what a gap in your schema looks like.
Step 3: Write Descriptions the Agent Acts On
Here's the thing that surprises most people: your descriptions are not documentation. They are the prompt.
TrustYou Agent builds the tool description the Agent reads by stitching your top-level field descriptions together, like this:
Fetch the guest's own information from Booking System. This system returns:
stage_of_booking- your description;refund_policy- your description; ... Call this when the guest's request touches any of this data.
So your descriptions reach the Agent twice, doing a different job each time. Before the call they form the tool description, which is what decides whether the Agent calls the tool at all. After the call they're rendered next to the returned values, along with which system they came from and how fresh they are, which is what decides what the Agent does with them. That second pass is why a description should carry your instruction and not just a definition: it's sitting right beside the value when the Agent is deciding what to say.
The What the agent sees preview panel next to the builder shows you the result as you type, and every description field carries a Read by the agent badge. Trust the preview.
Three rules earn their keep.
1. Say What the Field Means, Not Just What It Is
The Agent has to recognize that a guest's question touches this data. A key name alone won't do it.
- Weak:
refund_policy- "The refund policy." - Better:
refund_policy- "Whether this booking is refundable if the guest cancels."
The second version contains the words a guest's question is actually about. A guest asking "do I get my money back?" is far more likely to trigger a lookup against the second description than the first.
Only top-level descriptions make it into the tool description, so put the words that should attract a lookup on your top-level Fields and Groups. Descriptions on Fields inside a Group still reach the Agent once the data arrives, but they don't help it decide to fetch.
2. Mark Authoritative Fields as Authoritative
If your system is the source of truth, say so, or the Agent will politely double-check with the guest.
- Weak:
stage_of_booking- "Where the reservation stands." - Better:
stage_of_booking- "Where the reservation stands right now. This is authoritative. Never ask the guest to confirm their booking stage or whether they have checked in."
3. Be Directive About What to Do
When a field carries a decision your business has already made, phrase it as an instruction, not a label. Neutral phrasing gets treated as one input among many, and the Agent will helpfully offer alternatives you didn't want offered.
- Weak:
cancellation- "What to do with a cancellation request." - Better:
cancellation- "The resolved route for a cancellation. Follow it exactly. It is the only route you may offer. Do not add another, and do not ask the guest to confirm what it already states."
Same for booleans that should change the reply:
- Weak:
inform_no_refund- "Whether the guest gets a refund." - Better:
inform_no_refund- "When true, state in your first reply that the amount paid is not refunded. Do not wait for the guest to ask about money."
Those rewrites look heavy-handed. They are, on purpose, and they are the difference between an Agent that follows your routing and one that offers your route alongside two of its own.
When You Change a Description, Change One at a Time
Agent behavior varies between runs even with identical configuration. If you rewrite four descriptions at once and the Agent behaves differently, you've learned nothing about which rewrite did it. Change one description, test it, then move to the next.
Step 4: Connect It to an Agent
The connector exists at Organization level. Now bind it to the Agent that should use it.
- Open the Agent and go to the Features tab.
- Select the Guest Context Feature and turn it on.
- In the Connection panel, select Connect and pick your connector.
Until a connector is connected, the panel reads: "This feature sends its output through a connector. Connect one of your organization's connectors to activate it."
One connector per Feature, per Agent. If the connector is in an Error or Draft state you'll see a health warning here, and the lookup won't run.
Testing Your Setup
Test the Connector on Its Own
The connector detail page has a Test request card: "Fire one live request to check your URL, auth and schema. Nothing is stored and no conversation is affected."
Enter a Conversation context ID and a set of External identifiers as JSON, then select Send test request. The identifiers box validates as you type, so you'll know before sending whether your JSON is a valid object of string values.
The result is the important part. You get the response filtered to your schema, labeled "Filtered to your schema, this is exactly what the agent receives." Toggle Show filtered out and the fields your schema discarded appear grayed out, with the note "Grayed-out fields are removed by your schema and never reach the agent."
That toggle is your schema debugger. Use it deliberately:
Pass one, everything on. Point the test at a reservation where your endpoint returns the fullest possible payload. Turn on Show filtered out and read every grayed-out line. Each one is a field your endpoint bothered to send and the Agent will never see. For each, decide: declare it, or accept losing it. This is where you catch the field you declared on one Group but forgot on another.
Pass two, stripped down. Now test a reservation where your endpoint returns its thinnest payload. Guests who already checked out, bookings with no cancellation route, lookups that match nothing. Confirm that what survives is still enough for the Agent to answer, and that nothing your Agent depends on has quietly gone missing. A schema that only works on a full payload will fail on the half of your traffic that isn't full.
Pass three, the type check. Send one value in the wrong type on purpose. A number as "3", a boolean as "true", an enum value you didn't list. Watch it get filtered out. This is what a mismatch between your endpoint and your schema looks like in production, and it looks like nothing at all.
Read the Outcome Badge
Every test comes back with one of six outcomes:
| Badge | What it means |
|---|---|
| Success | The call worked and the response fit your schema. |
| No match · normal | Reached and authenticated, but no guest matched those identifiers. Not a configuration problem. |
| Authentication failed | Your credentials were rejected. Re-check the token, or the username and password. |
| Server error | The problem is on your endpoint's side. |
| Timeout | No response in time. Raise the timeout or check your endpoint's health. |
| Schema mismatch | A response arrived but didn't fit your schema. Adjust one or the other. |
No match · normal trips people up. It means everything is wired correctly and that reservation simply isn't in your system. Test with an identifier you know exists.
Test It in a Real Conversation
A green test request proves your plumbing works. It does not prove the Agent will ever call it. That's the description problem, and only a real Conversation shows it.
Open your Agent in Preview with a guest context bound, then:
- Ask the obvious question. Something squarely about a field you declared. The Agent should reply with a brief holding message, then answer properly on the next turn. If it answers immediately from general knowledge, it never called your webhook.
- Ask the same thing in the guest's words. Guests don't say "cancel my reservation," they say "something came up, we can't make it." If the direct phrasing triggers a lookup and the natural phrasing doesn't, your descriptions are too narrow. Widen them with the vocabulary guests actually use.
- Check it obeys, not just fetches. Ask about something where your endpoint returns a specific route. If the Agent fetches the data and then offers its own alternative alongside your route, the data arrived fine and your description wasn't directive enough. Go back to rule three.
- Ask a follow-up. The second question should be answered from the snapshot, with no holding message and no second call.
- Test an unknown guest. A Conversation with no identifiers should never attempt a lookup at all.
Note: Freshness caching means a repeat test within a few minutes reuses the previous result rather than calling your endpoint again. When you want a genuinely fresh call, start a new Conversation or wait it out.
When the Lookup Fails
Every failure path ends the same way: the Agent continues without the data. Your endpoint being down never blocks a guest from chatting.
What the Agent says in that moment is configured by TrustYou as part of your Agent's setup, not by you in the backoffice. A well-configured Agent apologizes briefly, points the guest at self-service or support, and states no policy it can't stand behind. That last part matters: without the guest's live data, quoting a general cancellation policy is a guess that might be wrong for their booking.
If you want that wording changed, talk to your TrustYou contact.
Limits Worth Knowing Up Front
Set expectations before you build:
- No per-Conversation trace of webhook calls. The backoffice does not show you, for a given Conversation, whether the webhook fired, what it returned, or how long it took. The Test request tool is your visibility, and it only works outside a real Conversation.
- No Insights signal. Lookups don't appear as a metric in your Insights dashboard.
- One connector per Feature, per Agent.
- Connectors are Organization-scoped, not per Place. Every Agent bound to the connector calls the same endpoint. If different properties need different endpoints, your endpoint has to branch on the identifiers it receives.
- POST only, fixed body. No custom headers beyond auth, no request templating.
Requiredis not enforced.- The response is filtered, never truncated. Size isn't your problem. Undeclared fields are.
What's Next
With live guest data flowing, make sure the rest of the Agent's knowledge holds up its end:
- Integrating TrustYou Agent Web Chat -- pass the identifiers that let the Agent recognize a guest in the first place
- How the Agent Works -- see where live guest context sits alongside your Knowledge Base
- Agent Settings to Review Before Going Live -- work through the pre-launch checklist
Comments
0 comments
Please sign in to leave a comment.