event_name and free-form params. If you already send events to GA4, you can reuse the same names and parameters.
Send your first event
1
Ask for the add-on
Events are enabled on request. Without the add-on, the API answers
403. Contact support.2
Copy the API key
Use your organization’s API key in the
x-api-key header.3
Send the event
Each request carries between 1 and 100 events. This one sends a purchase:
4
Check the result
The response has one result per event.
created means it was stored on the contact in contact.id. All the fields are in Send events.Body examples
Each example is the full body ofPOST /v1/events. You can mix events of different kinds in the same array, up to 100 per request.
- Purchase, cart, checkout and refund follow GA4’s e-commerce format: the amount in
value, the currency incurrencyand the products initems. The examples are in US dollars (USD); send your business’s currency as its ISO 4217 code, such asCOPorMXN. - Form and sign-up are the usual ones for a website that captures customers.
- Anonymous visit is a visitor who has not identified yet; see Visitors who have not identified yet.
- History loads past purchases with their real date.
- Existing contacts only adds the event without creating new contacts.
purchase, add_to_cart, begin_checkout, refund, generate_lead, sign_up, page_view, view_item), the contact’s activity already shows them with readable names, such as Purchase or Added to cart, with nothing to set up.
valueandcurrencycan go insideparams, as in GA4, or at the same level asevent_name, as in Existing contacts only. When both are sent, the outer one wins.occurred_attakes any ISO 8601 date with a time zone. Withoutoccurred_at, the event takes the time it arrived.- Everything you send in
paramsis stored as is. The contact’s activity shows the most important fields of each event, starting with the ones you choose as highlighted in the catalog. - The top-level fields of
params, such asstoreorpayment_method, can also be used in custom reports to filter and split. What goes insideitemscannot: to split sales by product or category, also send that field at the top level, such as"product_name": "Large pizza". - To see which events restaurants, clinics, lenders or schools send, and how they use them with campaigns, calls and reports, see Use cases with events.
How events are named
ContactShip stores every name in lowercase with underscores (snake_case), the style Google Analytics uses: purchase, add_to_cart, begin_checkout. If you send a name written some other way, it is normalized before it is stored, so the same event never ends up split into several:
Normalizing drops accents, splits words on capitals, spaces, hyphens or dots, and lowercases everything. The result must start with a letter and be up to 40 characters long:
1st purchase is rejected as invalid because it starts with a digit.
The response tells you the name each event was stored under in event_name and, when it changed, how you sent it in original_event_name.
Names starting with cs_ are reserved for the events ContactShip records itself (see below).
The ids you choose
Besides the contact’s data, an event can carry up to four ids that you define. Each one identifies something different:
Example: Ana visits your store, looks at two products and buys. Those are three events with the same
session_id, because they are the same visit, and the purchase also carries its event_id, purchase-10025.
The rules for all four:
- 1 to 200 characters.
event_idandsession_idcannot hold spaces. Spaces at the edges are trimmed; one in the middle makes the eventinvalid, with a message that says what to send.external_idandgroup.idaccept them, since they come as they are from your systems, but it is best to avoid them.- They are compared exactly:
CUS-9001andcus-9001are two different ids. - They belong to your organization: the same id in another organization is never mixed up.
event_id: unique, and the same on every retry
It identifies what happened, in your system. If a request fails and you retry it, the event with the same event_id answers duplicate and is not stored twice.
- Build it from the event and the id of what happened:
purchase-10025,refund-10025,payment_received-551902-3. It comes out the same on every retry without storing anything. - It must be unique across all your events, not just those with the same name: if the purchase and its refund carry just
10025, the refund comes backduplicateand is not stored. - If you prefer a UUID, generate it once and keep it with the record: a new one on every attempt does not prevent duplicates.
- It is optional, but without it there is no way to detect a retry.
session_id is explained in Visitors who have not identified yet; external_id, in How the contact is found; and group.id, in The contact’s company.
How the contact is found
Each event carries acontact object with at least one of these fields. ContactShip tries them in this order and keeps the first match:
- If several contacts share the phone, the event goes to the one with the most recent conversation or call.
- An email shared by several contacts is not used for matching.
- If the phone and the email point to different contacts, the phone wins and the result says so in
conflict. - If nobody matches and the event brings a phone or an email, the contact is created, named after
contact.name, or else the email or the phone. Creating contacts uses your plan’s contact quota. Withcreate_contact: false, the event is only added to contacts that already exist. - Contacts are never merged: an existing contact only gets its phone or email filled in when it was empty.
Examples by way of identifying
Only thecontact object changes. The response says how the contact was found in matched_by: contact_id, external_id, phone, email, session, or null when it was just created.
contact_id never creates a contact: if the id does not exist, the other fields are tried, and if there are none, the event is unresolved.
How an external_id gets linked. The first time, send it together with the phone or the email; from then on the id alone is enough:
external_id that was never linked finds nobody: if it arrives alone, the event is unresolved.
The contact’s company
If you sell to companies, send the contact’s company ingroup with each event. With it ContactShip builds companies: it brings together the contacts of each one and adds up what they bought.
idis required: the company’s id in your system (the tax id, the customer code or your CRM id), not its name. Up to 200 characters (see The ids you choose). It is how the company is recognized in the next events.nameis the name shown. If it changes, the company is updated.propertiesare the company’s data, such as the plan or the industry. Each event adds or updates the ones it carries and leaves the rest. They let you filter, for example, the contacts of companies on theenterpriseplan.- The first time an id arrives the company is created and linked to the contact. Another event with the same id doesn’t duplicate it.
- If you had already built companies from a contact property and one has the same name, that one is used.
Visitors who have not identified yet
“Viewed the catalog” or “added to cart” usually happen before you know who the person is. To keep that journey, send those events withoutcontact and with a session_id: the id of the visit, the same in all its events. Below is how to generate it.
1
Send the anonymous events
pending: they do not show up on any contact, filter or report yet, and they are kept for up to 30 days.2
Identify the session when you learn who it is
If at that moment you send an event with the contact’s data, such as the purchase, add the same If you have no event to send, because they logged in, signed up or finished a checkout you don’t send as an event, call
session_id to it. That identifies the session:POST /v1/identify:contact takes any of the fields of the lookup order, as in an event. The response says linked and, in pending_events, how many events were waiting. identify does not take group: the company goes inside an event.3
The events move to the contact
In less than a minute, with their original date, marked Before identifying in the activity. Anything arriving later with that
session_id goes straight to the contact, even without contact data.A session belongs to the first person who identifies in it. If data of someone else arrives later in the same session, for example on a shared computer, the session stays with the first one and the response says so:
already_linked in identify, conflict in an event. That is why it is best to generate a new session_id on logout.anonymous_daily_limit until the next day; if you need more, contact us. Requests that carry anonymous events also use a request budget of their own, smaller than the rest of the events (Rate limits).
How to generate the session_id
Your site or app generates it, not ContactShip. The simplest is one UUID per browser, created the first time and reused in every event:
- If you already use Google Analytics, its client id works too: the
_gacookie, for exampleGA1.1.1234567890.1726000000. - No spaces, 1 to 200 characters. A
session_idwith a space in the middle makes the eventinvalid, and makesidentifyanswer400. - It is compared exactly: case matters.
- Never use something that identifies the person, such as their email or phone: if you have it, send it in
contact.
What each result means
An event with problems does not affect the others in the same request.
Events ContactShip records
Besides the ones you send, ContactShip records what happens inside the platform. They show up in the contact’s activity and can be used in filters and reports just like yours:
That is why you can ask questions that cross both worlds, such as “bought within 7 days of receiving the campaign”.
Good practices
- Always send
event_id, such aspurchase-10025: unique across all your events and the same on every retry. That way you can retry a failed request without duplicating events. - Send
event_idandsession_idwithout spaces: one in the middle makes the eventinvalid. - Load history with
occurred_at. Past events count for segments and reports as if they had arrived at the time. - Send the phone in international format. It is the field that best identifies the contact.
- Use GA4 names (
purchase,add_to_cart,begin_checkout,sign_up) and, for purchases,transaction_id,value,currencyanditems. Sales reports look forpurchasefirst. - Send the currency with every purchase. Amounts in different currencies are never added together.
- Use the same
session_idin every event of a visit and in theidentifycall. - Send the company in
groupif you sell to companies: without it, purchases stay only on each person. - Batch up to 100 events per request. This endpoint has its own request budget, separate from the rest of the API.
FAQ
Why does my event show up under another name?
Why does my event show up under another name?
Because it was normalized:
addToCart or Add To Cart are stored as add_to_cart. Check original_event_name in the response to see how it arrived.Can I send events from visitors who have not identified yet?
Can I send events from visitors who have not identified yet?
Yes, with a
session_id and no contact data. They wait for up to 30 days and move to the contact when the session is identified. See Visitors who have not identified yet.Which names can I not use?
Which names can I not use?
Names starting with
cs_: they are reserved for what ContactShip records itself, such as calls and campaigns.Do events cost credits?
Do events cost credits?
No. Events are included in the plan. Creating new contacts does use your contact quota.
What happens if I send the same event twice?
What happens if I send the same event twice?
If it carries the same
event_id, the second result is duplicate and it is not stored again. Without event_id there is no way to detect it.Can I create an event in the app, without sending it?
Can I create an event in the app, without sending it?
No. Each event shows up in the catalog the first time it arrives. To see one before integrating, use the test event.
Next
Send events
Every field, result and error.
Identify a session
Join the anonymous events of a visit to a contact.
Contact events
Where they show up and how to filter contacts by what they did.
Sales reports
Sales, repeat purchases and sales credited to ContactShip.
Use cases with events
What to send and what to do in restaurants, clinics, collections and more.
