The Client Timeline and Events from Your CRM
The Timeline tells you what happened to a client, in order: when the contract started, when it was paused, when a payment came in and how late it was, when a notification went out. You never have to write anything into it — it fills itself.
And when your CRM knows things SalesControl cannot — an onboarding call booked, accesses handed over, a campaign approved — you can let it tell the story too, through a webhook: every event you send becomes a Timeline entry, with its own date and time.
The Timeline and event webhooks need a paid plan (Starter and above). See Subscription Plans.
Where to find it
Open a client from the Clients list and pick the Timeline tab. Each row shows the event and the moment it was recorded, in your own time zone.
It is not the same as the Logs tab. That one is the full technical record, restricted to people who hold the log permission, and it includes amounts and internal details. The Timeline tells the facts of the relationship without the confidential details — which is why the whole team can see it.
What gets recorded on its own
With nothing to configure, the Timeline already receives what SalesControl knows:
- The contract: created, started, renewed, paused and resumed, expired, terminated. Expiry accounts for pauses, so it is the real date rather than the theoretical one.
- The contract sent for signature: Contract sent for signature, one Contract opened by the client entry for every opening of the link (from the second one on with the number, “opening no. 2”, then the location and the reading time when available), Contract signed, Contract sending cancelled if you cancel it, Offer expired unsigned if the link expires before the signature, Contract abandoned and Contract restored if you move it to Abandoned and bring it back (these two are internal organisation: they never reach the client’s PDF or chat). If the contract had been sent to the lead, on conversion these entries move to the client like the others. The details of every opening are in the opening history.
- Payments: collected (with the days late, when there were any), marked unpaid, reversed, reconciled from Stripe.
- The trial: campaign activation, passed or failed outcome.
- Material contract changes: service, duration, value, payment type, start date or price — that is, every time the instalments get rebuilt.
- Notifications sent: one row per send, listing the channels that actually delivered.
- The lead history: when the client was born by converting a lead, at conversion the whole story of the lead phase gets copied here with its original dates — Lead created (with the source), Appointment booked, Appointment rescheduled or Appointment cancelled, any Lead lost and Lead reopened, up to Lead converted to client. Reassignments and scheduled follow-ups are carried over too, but they only show in the Logs tab: they are internal organisation, not facts about the client.
That history is a copy, not a link: if the lead is later deleted, the events carried over to the client stay where they are. And converting the same lead a second time creates no duplicates.
The Timeline starts on 19 August 2026: earlier events are not reconstructed after the fact. A contract signed in June will have no “contract started” row, but everything from here on is recorded. The lead history has a birth date of its own: its record exists since 28 August 2026, so a lead worked before then only brings along what was recorded from that day onwards.
Events from the ecosystem (GodAds)
If your agency also uses GodAds and the two platforms are linked through the Ecosystem, the Timeline can also receive events coming from there. They only touch clients SalesControl matches with certainty (the rest wait in the Unmatched clients list of the Ecosystem card, where you match them by hand) and depend on two switches the Admin finds in that same card, both off until switched on.
- Campagne lanciate (GodAds) — “campaigns launched”; the row name is written by the server in Italian. With the Campaigns launched → client trial automation on, when GodAds launches the campaigns of a matched client the row lands in the Timeline. If the client is on an active trial and the “campaigns activated” date is still empty, it gets filled in automatically with the launch date (a Campaigns activated row appears too); a date already set by hand is never overwritten, and the trial outcome is never touched.
- The informational events — with Informational events from GodAds on, seven kinds of row arrive as pure narration, never an action. Their names too are written by the server in Italian: Wallet ads in esaurimento (GodAds) (ads wallet running low), Wallet ads esaurito (GodAds) (ads wallet empty), Campagne messe in pausa manualmente (GodAds) (campaigns paused manually), Campagne riattivate manualmente (GodAds) (campaigns resumed manually), Eliminazione campagne programmata (GodAds) (campaign deletion scheduled), Eliminazione campagne annullata (GodAds) (campaign deletion cancelled), Campagne eliminate (GodAds) (campaigns deleted). Mind the wording: “deletion” always refers to the client’s campaigns, never to the client.
How the ecosystem gets linked, how the automations are enabled and how waiting events get matched is covered in Linking SalesControl to the Ecosystem (GodAds).
Bringing in your CRM events
In the Admin Panel, Integrations tab, the Custom events card creates the addresses your CRM can post to.
If you have connected the GoHighLevel app you do not need a webhook for each event: in the GoHighLevel tag events card you decide which tag, added to a contact in the agency’s or a client’s sub-account, writes which event in the Timeline. Tag events that find no client go to the same queue below, labelled GHL tag.
New webhook, and give it a name you will recognise (“Onboarding”, “Accesses delivered”). Create as many as you like: one per kind of event keeps your automations tidy.
It ends with /functions/v1/client-event-intake?t=…. The part after t= is that webhook’s token: whoever holds it can write events into your agency, so treat it as a credential.
In GoHighLevel: add the Webhook action to a workflow, method POST, then add the custom fields. In Zapier or Make: a POST request to the same address with a JSON body.
How to set it up opens two ready JSON bodies, one for each of the cases below, each with its own copy button.
Two cases, two templates: who is sending?
This is the part that matters most, and the only place where you can get it wrong without noticing.
When the workflow runs from the client sub-account
The normal case: the client has their own GoHighLevel sub-account and the automation runs there. The location variables point at them, so those are enough:
{
"event": "Contratto firmato",
"location_id": "{{location.id}}",
"location_name": "{{location.name}}",
"external_event_id": ""
}
On the first event SalesControl matches the client by name and learns the sub-account identifier: from then on every following event finds them immediately, even if the name in your CRM changes.
When the workflow runs from the agency sub-account
Some procedures about a client run from your sub-account — an onboarding call you book yourself, say. There the location variable is the agency, not the client. Declare it with location_type and name the client:
{
"event": "Onboarding prenotata",
"location_type": "agenzia",
"cliente_nome": "Nome del cliente",
"contact_id": "{{contact.id}}",
"external_event_id": ""
}
The contact_id is what makes every later send automatic. In your own sub-account the contacts are your clients: on the first event SalesControl matches the client by name and learns that contact; from then on the name is no longer needed, and the event lands correctly even if the client gets renamed in GoHighLevel. Leave it out and everything still works — the name will simply be compared every time.
Without that declaration, SalesControl would take the agency location for the client’s and link it to the first client matched by name. From that moment every event sent from your sub-account — including those about other clients — would land on them, with no visible error. With location_type set to agenzia the location is kept but ignored: it neither looks up the client nor ever gets linked.
The value to write is agenzia (agency works too). From client sub-accounts you simply leave the field out: its absence already means “I am the client”.
Accepted fields
| Field | Recognised keys | What it does |
|---|---|---|
| Event name | event, evento, event_name, event_type, action | The text that will appear in the Timeline. It may be omitted if the webhook has a default event name. |
| Sub-account id | location_id, ghl_location_id, sub_account_id, account_id | The client sub-account. Once linked on the first match, this alone is enough. |
| Sub-account name | location_name, sub_account, account_name | Used for the first match, to recognise the client by name. |
| Sub-account type | location_type, tipo_location | Write agenzia when sending from your own sub-account. Leave it out otherwise. |
| Client | cliente_nome, client_name, customer_name, or cliente_id | Names the client. This is the way to go when sending from the agency account. |
| Contact | contact_id, ghl_contact_id, id_contatto | Agency sends only: the contact standing for the client. Linked on the first match, and from then on this alone is enough. |
| Event id | external_event_id, event_id, idempotency_key | Deduplication key: if the same identifier comes back, the event is not recorded twice. |
Key names are read case-insensitively. In GoHighLevel the workflow custom values land inside customData: the webhook reads those, the top level and contact alike — and a custom field left empty never wipes out the standard value of the same name.
The date and time are those of arrival, taken from the server clock: that is what makes the Timeline evidence rather than a claim. If you want to record that something actually took place, send a second event at that moment — “Onboarding booked” when it is booked, “Onboarding held” when it happens.
How an event finds its client
- If the sub-account is already linked to a client, the match is immediate.
- Otherwise the name is compared, ignoring case, accents and double spaces. Exactly one match: linked, and the connection is learned. Several candidates, or none: the event waits.
- When the event comes from the agency account the same rules apply with the other key: the contact if it is already linked, otherwise the client name — and what gets learned is the contact, never the location.
The two never swap places, and that is deliberate. In a client sub-account the location is the client, while the contacts are their leads: learning one would mistake a lead for the client. In your own sub-account it is the other way round — the location is you, the contacts are your clients.
The events waiting to be matched
Events that find no client are not lost: they stay in the Custom events card, under Events to match, showing the name and identifier that arrived. Pick the client and confirm: the event enters the Timeline with its own original date, not today’s.
If the event carried a client sub-account, matching it also links that sub-account, and the other waiting rows from the same source are settled along with it. The same holds for events that arrived from the agency carrying a contact_id: matching one links the contact, and the other rows from that contact are settled along with it. Without a contact, those events are matched one at a time: the agency location belongs to no client, so it cannot stand in for the others.
The link to your CRM
When a client is linked to a sub-account, their Timeline shows a Linked to your CRM label with an Unlink button. If they are linked to a contact as well — that is, if their events reach you from your own sub-account — the Linked to contact label sits beside it, with its own Unlink.
Unlinking stops events from arriving automatically by that route — they will go back to the matching queue — while the events already recorded stay where they are. The two links are independent: you can undo one and keep the other. Use it when a link was learned onto the wrong client, or when a client moves to a different sub-account.
Showing events in the client chat
Events can also appear in the client chat, as annotations in the middle of the conversation rather than messages from someone. The client only ever sees the event name, never the details.
- The agency-wide preference lives under Settings › Client support chat, in the Events in the chat card, with the Show events in client chats switch, and applies to everyone.
- In the same card you pick which events appear: each has its own switch, grouped into Contract, Trial and campaigns, Payments and Custom. Out of the box they are all on; the ones you turn off appear in no chat, not even for clients where events are turned on one by one.
- Webhook events can also be picked by name: under the Event switch, in By event name, you find every name your webhooks have sent, with how many times it arrived and the date of the last one. Turn a name off and its events stay in the Timeline only; with Event off, none of them appear. A name shows up in the list after the first event is received.
- On a single client, in their Timeline, you can choose Always or Never, or leave As the agency.
Only some events can reach the chat: signing steps, appointments, the lead history and internal corrections always stay in the Timeline only.
Taking the Timeline out
- Download PDF produces a printable record, handy to attach to a report or bring to a meeting.
- Send to group posts the summary into the client chat. The message cannot be deleted, so the confirmation tells you first how many events will be sent.
If something does not work
- Your tool gets a 404: the address is wrong or incomplete, the token was regenerated, or the webhook is switched off.
- 403: the agency is on the Free plan, which does not include custom events.
- 422: the event name is missing and the webhook has no default one.
- Events arrive but all stay in the queue: the name you send probably does not match the one the client is registered under here. Match one by hand — the link is learned and the rest sort themselves out.
- Events for different clients all land on the same one: you are sending from the agency sub-account without
location_type. Add it to the workflow, then open the wrong client and use Unlink in their Timeline. - The Timeline tab is missing: your plan does not include it.