Loading history out of a warehouse or a bucket rather than firing events one at
a time? Start with Load events from your data
warehouse — the bulk import
endpoint takes the same four event
shapes, 1000 per request, and gives you a job to poll.
Getting your webhook URL
In Octave, go to Settings → Integrations and find the section for the kind of event you want to send. At the bottom of each section is “Don’t see your provider? Send events directly via the Octave API.” — click Set up. The panel gives you your workspace API key and the webhook URL for that event type, already carrying the key.The URL includes your API key as
?api_key=…. To send it as a header instead,
drop the query string and pass api_key.The four event types
Each type has its own endpoint. POST a JSON body matching the schema for that type — the Log Event reference is generated from the API’s own schema and lists every field with a description and example. The sections below cover the fields that matter most and why.Only
sent and reply email events, and transcript call events, are
processed for analytics. The rest are accepted and stored, but do not produce
findings.Reading the response
A payload that fails validation is answered with HTTP 200 andreceived: false,
with the per-field messages under validationErrors. Check data.received in your
integration rather than the status code alone — an automation that only looks for a
2xx will report success on every rejected event. The request body is limited to 10 MB.
Email events
eventTimestamp, eventType, subject, body, from, to.
Send only the new part of each message. Mail clients quote the whole thread
below every reply; if that arrives in body, the same paragraphs are read once
per message in the thread and every insight from them is counted that many
times. Include cc and every to recipient as well — participants are how
Octave knows who was on the conversation — and give related messages the same
conversationId so they read as one thread.
Call events
Send the transcript as an array of speaker turns when you have them. It is faster and more accurate than a flat string, which Octave has to diarize with an LLM before it can attribute anything.eventTimestamp, eventType, title, transcript, participants.
List everyone who speaks in participants, under the same name the transcript
uses for them, with role set by domain (your own people internal, everyone
else external). Octave decides who is your team and who is the customer from
this list — a rep who is missing from it, or listed under a login name the
transcript never uses, can end up attributed to the customer’s side. Calendar
invitees are a poor source for it; the meeting platform’s attendee list is the
right one.
CRM events
CRM events are different from the other three in one important way: a deal is one object that changes over time, not a one-off action. Read the Keeping deals up to date section below before you build against this endpoint.eventTimestamp, eventType, opportunityId, opportunityName.
Keeping deals up to date
Re-send a deal every time it changes, and includecrmLastModifiedAt.
Octave dedupes on a per-event ID. For emails and calls that is straightforward,
because one send is one event. A deal is not: the same opportunityId comes
back again and again as the amount moves, the stage advances and the close date
slips. crmLastModifiedAt is what tells those pushes apart — without it, an
updated deal can look identical to the one before it and be discarded as a
duplicate, leaving your pipeline view frozen at the values it was first created
with.
If your system doesn’t expose a last-modified timestamp, Octave falls back to
hashing the deal’s mutable fields, so genuine edits still land. Sending the
timestamp is more reliable, and it is cheaper: the hash covers every field that
can change, so a field Octave cannot see a timestamp for still counts as a new
state. A last-modified timestamp that only moves on real edits keeps the event
stream tighter than a hash that reacts to any field at all.
Sending the same deal repeatedly is expected and safe. Octave records a deal’s
won, lost and created milestones once per opportunity, so re-sending a closed
deal after an owner reassignment or a late-entered loss reason updates the deal
record without adding a second “won” to your reporting.
Fields worth sending
These are optional, but each one measurably improves what Octave can do:contactEmails— every contact on the deal, not just the primary. Event linking matches on all of them, so this is the single biggest lever on how many emails and calls attach to the opportunity.crmAccountIdandcrmAccountDomain— matching on account name alone cannot separate two deals at companies with the same name, or the same company under two domains.stageCategory— one ofopen,closed_won,closed_lostorother. Without it the stage bucket is inferred fromeventType, so a deal deep in procurement still reads as simply “open”.lossReason— powers win/loss analysis on closed-lost deals.mergedOpportunityIds— when your system merges deals, list the ones merged into this deal. Octave folds their history in and tombstones them, so a merge doesn’t leave duplicates counting toward pipeline.
Social events
eventTimestamp, eventType, body, from, to.
Connecting activity to deals
By default Octave works out which opportunity an email, call or social touch belongs to by matching participants and company domains inside a time window. That is a reasonable guess, and it is still a guess — it misses when the person on the thread isn’t on the deal, and it can attach to the wrong one when two deals share a domain. If you already know the deal, say so. Every email, call and social event accepts:crmOpportunityId— your system’s deal IDcrmAccountId/crmAccountDomain— your system’s account ID, or the account’s domain
crmOpportunityId matches a deal Octave knows about, the event is linked
to it directly and the inference is skipped. An ID that doesn’t match anything
falls back to the normal matching, so a stale ID never leaves an event
unlinked.
crmAccountId and crmAccountDomain name the account without naming a deal.
Octave narrows its inference to that account’s opportunities, so the event
lands on the right deal even when none of its participants are on it — the
usual position for a call platform, which knows the account a meeting was
booked against but not the opportunity. Send crmOpportunityId whenever your
source knows the deal, and the account fields whenever it only knows the
account.
All three are optional. Activity with no deal behind it — a support escalation,
a quarterly check-in — is still worth sending with the account fields: it stays
attributed to the account instead of floating free.
Timestamps
SendeventTimestamp as ISO 8601 with an offset — 2026-08-26T14:00:00Z. A
timestamp with no offset or Z (2026-08-26 14:00:00, 2026-08-26) is read as
UTC, never as anyone’s local time; attach the offset if your source stores
local times.
Send the moment the event happened, not the moment your system recorded it. A
CRM task’s created date is when the task was logged, which can be hours after
the email went out and identical across a whole thread synced in one pass — and
it is the email’s own sent date that puts a conversation in order.
Deduplication
Every event gets an ID. Send one yourself aseventId if you have a stable
identifier — it is the most predictable option, and lets you re-send safely.
If you don’t, Octave derives one from the event’s content. Re-sending identical
content is treated as a duplicate and ignored; changed content is a new event.
Two things to know about IDs:
- They are unique per workspace across every event type. An email and a
call may not share an
eventId. If your row IDs could collide across sources, prefix them (zoom-…,sfdc-task-…). - A re-send never updates. Once an ID has been ingested, sending it again
with corrected data is skipped as a duplicate rather than applied. To correct
an event that already landed, send the fix under a new
eventId, or ask us to erase the original first and then re-send it unchanged.
Troubleshooting
Events accepted but nothing appears. Check the event type is one that gets processed. Emails produce findings onsent and reply, calls on transcript,
and CRM on opportunity_created, deal_won, deal_lost and meeting_booked.
Other event types are stored but do not feed extraction.
A deal shows only one “won” despite several sends. That is deliberate. Each
milestone is recorded once per opportunity, so later sends update the deal
record without duplicating the milestone in reporting.
Deal values look stale. You are almost certainly re-sending deals without
crmLastModifiedAt. See Keeping deals up to date.
Activity isn’t attaching to the right deal. Send crmOpportunityId on the
email, call or social event rather than relying on participant matching — or
crmAccountId when your source only knows the account.
Nothing arrives at all. Confirm the API key in the URL matches the workspace
you’re looking at, and that you’re POSTing to the endpoint for the right event
type. Then check data.received on the responses — a rejected payload is a 200
with received: false, not an error status.