Skip to main content
Octave connects directly to the common GTM tools. When your source isn’t one of them — an in-house system, a tool we don’t support yet, or an automation you built in n8n, Clay or Make — the generic events API takes the same four kinds of activity over plain HTTP. Events sent this way behave exactly like events from a native integration: they extract findings, match to your library, attach to deals, and feed outcomes.
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 and received: 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

Required: 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.
Required: 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.
Required: eventTimestamp, eventType, opportunityId, opportunityName.

Keeping deals up to date

Re-send a deal every time it changes, and include crmLastModifiedAt. 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.
  • crmAccountId and crmAccountDomain — matching on account name alone cannot separate two deals at companies with the same name, or the same company under two domains.
  • stageCategory — one of open, closed_won, closed_lost or other. Without it the stage bucket is inferred from eventType, 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

Required: 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 ID
  • crmAccountId / crmAccountDomain — your system’s account ID, or the account’s domain
When 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.
Use the same IDs you send on your CRM events. They’re matched against the deals in your workspace, however those deals arrived — so this works even if your deals sync from HubSpot or Salesforce while your activity comes through this API.

Timestamps

Send eventTimestamp 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 as eventId 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 on sent 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.