Event ingress
Event ingress takes signups, purchases, refunds, and email addresses from your own server. ChannelGraph then credits the Content that brought each buyer in.
POST https://app.channelgraph.io/api/v1/ingress/events
Your server signs each call with a Workspace key.
- In ChannelGraph, open Settings, then API.
- Enter a name for the key, such as the name of your server, and click Create.
- Copy the key. ChannelGraph shows it once and never again.
A key starts with cgk_.
It belongs to one Workspace, and every event it sends goes to that Workspace.
Each member of the Workspace can create and revoke keys.
A Workspace holds up to 50 keys that are not revoked.
Send the key in the Authorization header:
Authorization: Bearer cgk_...Keep the key on your server. Never put it in a web page or an app. When a key leaks, revoke it on the same screen and create a new one.
Send an event
Section titled “Send an event”curl https://app.channelgraph.io/api/v1/ingress/events \ -H "Authorization: Bearer $CHANNELGRAPH_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "purchase", "offer_id": "p4c6e8g0j2l4n6q8", "email": "buyer@example.com", "order_id": "1001", "amount": "49.00", "currency": "USD" }'Response 202:
{ "event_id": "purchase:order:p4c6e8g0j2l4n6q8:1001" }A 202 means that ChannelGraph stored the event.
Do not send it again.
An Offer’s ID is the last part of the address of its page in ChannelGraph.
Event types
Section titled “Event types”| Type | What it records | Needs |
|---|---|---|
signup |
A signup for an Offer. | offer_id |
purchase |
A purchase of an Offer. | offer_id |
refund |
A full or a partial refund of a purchase. | offer_id and order_id |
identify |
An email address for a browser or a click. It records no conversion. | email |
A signup or a purchase also needs email or token, or ChannelGraph cannot tell who converted.
A purchase with an order_id is the one exception.
ChannelGraph records it even without email or token.
Event fields
Section titled “Event fields”| Field | Type | Limit | Meaning |
|---|---|---|---|
type |
string | signup, purchase, refund, or identify. Required. Case does not matter. |
|
event_id |
string | 1 to 64 characters | Your own ID for the event. See idempotency. |
offer_id |
string | 32 characters | The ID of an Offer in the Workspace. Required for signup, purchase, and refund. |
email |
string | 320 characters | The person’s email address. Required for identify. |
order_id |
string | 200 characters | The order or payment ID from your checkout. Required for refund. |
amount |
string or number | 32 characters | The amount in major units, such as "49.99". Zero or more. |
currency |
string | 3 letters | The currency code, such as USD. Case does not matter. |
ts |
string | 64 characters | When the event happened, in ISO 8601, such as 2026-01-31T12:00:00Z. |
token |
string | 64 characters | The cg_ or cv_ value that the page or the checkout carried. See tokens. |
- A time without an offset is in UTC.
- Without
ts, the event takes the time it arrived. tsmust be no more than 400 days in the past and no more than one hour in the future.- ChannelGraph ignores a field that it does not know. Thus a sender keeps working when a new version adds a field.
Tokens
Section titled “Tokens”A token ties an event to the click or the browser that brought the person in.
| Token | Comes from |
|---|---|
cg_ followed by 16 characters |
A ChannelGraph Link. The Link adds utm_content=cg_... to the address it opens. |
cv_ followed by 22 characters |
The ChannelGraph snippet on your site. The snippet adds it to the checkout links on the page. |
The snippet puts the cv_ value into these checkout fields:
| Checkout | Field |
|---|---|
| Stripe Payment Links | client_reference_id |
| Lemon Squeezy | checkout[custom][cg] |
| ThriveCart | passthrough[cg] |
Read the value back from the order, and send it as token.
For example, read client_reference_id from the Stripe Checkout Session.
ChannelGraph uses a cg_ click only when the click belongs to your Workspace.
It uses a cv_ browser only when your Workspace has seen the browser.
Any other non-empty token fails with 422.
- Send every amount in major units, such as
"49.99", never in cents. - Send the amount as a string to keep its exact digits.
- An amount without
currencytakes the currency of the Offer’s price. With no price either, the purchase has no amount. - An amount above 1,000,000 counts as no amount.
- A purchase with no usable amount counts the Offer’s price.
- A refund without an amount refunds the whole purchase.
- A refund in another currency than its purchase does not change the purchase.
- Refunds never take back more than the purchase counts.
Idempotency
Section titled “Idempotency”ChannelGraph stores each event once.
A retry of a stored event answers 202 with the same event_id and records nothing new.
An event’s ID comes from the first rule that applies:
| Event | ID |
|---|---|
Any event with event_id |
Your event_id. |
A refund with order_id |
refund:order:<offer_id>:<order_id>:<amount>. Without an amount, the last part is full. |
Any other event with order_id |
<type>:order:<offer_id>:<order_id>. |
| Any other event | <type>:sha256:<hash>. The hash covers the fields you sent and the event’s time. |
Thus an order_id makes a retry safe without an event_id.
An event with no event_id, no order_id, and no ts takes a new time on each call.
Each such call is a new event.
Two partial refunds of the same amount on one order get the same derived ID.
ChannelGraph then stores the second one as a retry of the first.
Send a distinct event_id for each refund, such as the refund ID from your checkout.
Event ingress and the Automation API share one event ID space in each Workspace.
ChannelGraph also counts each conversion once:
- A purchase with
order_idcounts once for each order of each Offer. - A purchase without
order_idcounts at most once for each person, Offer, and UTC day. - A signup counts once for each person and Offer.
Examples
Section titled “Examples”A signup:
curl https://app.channelgraph.io/api/v1/ingress/events \ -H "Authorization: Bearer $CHANNELGRAPH_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "signup", "offer_id": "a1b2c3d4e5f6g7h8", "email": "reader@example.com", "token": "cg_Ab3dE5gH7jK9mN1p" }'A partial refund:
curl https://app.channelgraph.io/api/v1/ingress/events \ -H "Authorization: Bearer $CHANNELGRAPH_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "refund", "event_id": "re_3PqR8s", "offer_id": "p4c6e8g0j2l4n6q8", "order_id": "1001", "amount": "19.00", "currency": "USD" }'An email address for a browser:
curl https://app.channelgraph.io/api/v1/ingress/events \ -H "Authorization: Bearer $CHANNELGRAPH_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "identify", "email": "reader@example.com", "token": "cv_Xq3v9LmA0bC1dE2fG3hI4j" }'Errors
Section titled “Errors”An error body has a detail string that names the problem.
{ "detail": "order_id: a refund names the order it reverses." }| Status | Meaning |
|---|---|
401 |
The key is missing, unknown, or revoked. detail is Send a live key as Authorization: Bearer cgk_.... |
413 |
The body is larger than 16 KiB. |
422 |
The event is not valid. detail names the field, such as email: this is not an email address. |
429 |
The key sent more than 600 events in this minute. Wait for the next minute, then send again. |
A 422 also covers a body that is not a JSON object.
Do not retry a 401, 413, or 422 without a change.
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Events for each key | 600 a minute |
| Body size | 16 KiB |
| Keys that are not revoked in one Workspace | 50 |