When to use it
- “Marcus met Priya at the dinner. She is evaluating in Q1.”
- “Dana never actually showed up, even though Luma says checked in.”
- “I emailed Theo the deck on Tuesday, log it.”
- “I sent Jane a personal invite on LinkedIn yesterday.”
- After you sent a nudge to an owner through the user’s chat or mail connector, with the message id it returned.
Parameters
Each item has
person (the email this workspace holds for them) and any of these kinds. One item can carry several kinds; each becomes its own fact.
Every
at is RFC 3339 with its offset, for example 2026-10-13T09:30:00-05:00. An at later than now is refused.
Workflow
1
Resolve the collab
Take
channel_id from list_events. If the user named the event, event_name works, but an ambiguous name records nothing and lists the candidates.2
Check each person is in the workspace
person must be an email this workspace holds. A person heyBTW has no record of is rejected; add them first with import_people.3
Build one item per person
Put only facts the user stated or you saw. A second follow-up or invite for the same person is another item.
4
Generate a client_request_id and call once
On a transport error, retry with the SAME
client_request_id. A committed call comes back with idempotent_replay: true and is never repeated. Reusing the id for different observations is refused.5
Report recorded and rejected
Say how many facts were recorded and name each rejected reference with its reason.
Response
The success shape below is from the code (tools/record_observation.py and observation-contract.json), not a live call: this page was written against a production workspace, so nothing was recorded. data[0] holds:
Keep each
observation_id: save_task takes it as observation_ref when a follow-up comes out of the debrief. Read observations back with get_event_details (include_contacts), where notes, follow-up summaries and nudge proofs appear on the contact rows. A partner of the collab never sees them.
The validation error below is a real response, trimmed, from a call that sent a channel_id and no observations:
Rules the agent must follow
- An invite is never an RSVP and never a touch. It never changes attendance, a registration or a funnel count. Record one only for a send that happened or that the user states, never from a draft or from list membership.
- Never record again an invite that activate_list sent, or a List the user sent outside heyBTW that was recorded with activate_list
destination_type: "external". activate_list already logs each of those invites. - A nudge is never a touch. It is your ask going out to the person’s owner, recorded as
nudged, never asfollowed_up, and only with themessage_refthe connector returned. Without that proof it is a draft: record nothing. followed_uprecords what the CRM shows, not proof of contact: an outbound email, call, meeting held, note or sequence step you saw, or one the user stated. Never an inbound reply, never inferred from a completed task or a sent invite. When a CRM is connected in heyBTW, its own read stays the source for follow-up status.attended_confirmedsits beside the platform’s attendance and never overwrites it. Only CHECKED_IN on the platform is attended.- Observations are append-only: a later one adds, never replaces.
Errors and gaps
Related
- save_task: turn an accepted follow-up into a task, pointing at the
observation_id. - get_attendee_touch_status: recorded follow-ups appear there as
recorded_follow_ups. - activate_list: the path for List sends, which records its own invites.
- Playbooks: The morning after, Follow-up and ROI.