Skip to main content
An event is something a contact did outside ContactShip: a purchase in your store, an order in your POS, a form, a visit to your website, a step in a Make or n8n flow. You send it to the API with a name and any data you want, and it shows up in the contact’s activity. Then you use it to segment contacts, send campaigns and build sales reports, without integrating each system separately. The format is Google Analytics 4’s: an 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.
Want to see how an event looks before writing any code? In Settings → Organization → Events, click Send a test event. See Events catalog.

Body examples

Each example is the full body of POST /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 in currency and the products in items. The examples are in US dollars (USD); send your business’s currency as its ISO 4217 code, such as COP or MXN.
  • 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.
With the names in these examples (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.
  • value and currency can go inside params, as in GA4, or at the same level as event_name, as in Existing contacts only. When both are sent, the outer one wins.
  • occurred_at takes any ISO 8601 date with a time zone. Without occurred_at, the event takes the time it arrived.
  • Everything you send in params is 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 as store or payment_method, can also be used in custom reports to filter and split. What goes inside items cannot: 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_id and session_id cannot hold spaces. Spaces at the edges are trimmed; one in the middle makes the event invalid, with a message that says what to send. external_id and group.id accept them, since they come as they are from your systems, but it is best to avoid them.
  • They are compared exactly: CUS-9001 and cus-9001 are two different ids.
  • They belong to your organization: the same id in another organization is never mixed up.
Don’t use a name as an id. ACME Warner Bros as group.id works, but the day the name changes or arrives spelled differently you would have two companies. Use the id your system already gives it: the tax id, the customer code, the CRM id.

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 back duplicate and 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.
The 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 a contact 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. With create_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 the contact 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:
An 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 in group with each event. With it ContactShip builds companies: it brings together the contacts of each one and adds up what they bought.
  • id is 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.
  • name is the name shown. If it changes, the company is updated.
  • properties are 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 the enterprise plan.
  • 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 without contact 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

Each result is 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 session_id to it. That identifies the session:
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 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.
An organization’s anonymous events are capped at 100,000 per day. Past that number they are rejected with 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 _ga cookie, for example GA1.1.1234567890.1726000000.
  • No spaces, 1 to 200 characters. A session_id with a space in the middle makes the event invalid, and makes identify answer 400.
  • 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.
The API key never goes in browser code. Your site passes the session_id to your server, and your server calls the API.

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 as purchase-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_id and session_id without spaces: one in the middle makes the event invalid.
  • 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, currency and items. Sales reports look for purchase first.
  • Send the currency with every purchase. Amounts in different currencies are never added together.
  • Use the same session_id in every event of a visit and in the identify call.
  • Send the company in group if 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

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.
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.
Names starting with cs_: they are reserved for what ContactShip records itself, such as calls and campaigns.
No. Events are included in the plan. Creating new contacts does use your contact quota.
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.
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.