S Subster

Send an event to us

You will tell us from your server about a registration, install or purchase in the app, so that the event reaches ad networks once.

Some events happen where we are not present: in the app and on your server. The ad network does not learn about them, and traffic buying learns from incomplete data.

Your server reports an event in one request, and we send it as a conversion to the connected ad networks and as a postback to the partner's tracker, if the project owner set its address. Whether the conversion goes to a network depends on the customer's tracking consent.

Before you start

What to send

EventWhen to send
registrationthe person created an account in the app
app_installedthe app was installed
purchasea purchase inside the app or on your server

Send only what happened on your side. We send a purchase on our paywall to the networks ourselves: your event about it would become a second conversion.

A purchase event does not open access in the app for the customer: it is a message for ad networks and the partner.

Request

curl -X POST "https://api.subster.ai/public/event" \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "event": "purchase",
    "projectId": "<PROJECT_ID>",
    "eventId": "ios-iap-2000000712345678",
    "guid": "abc123def456",
    "amountCents": 1999,
    "currency": "usd",
    "consent": "granted"
  }'
FieldRequiredWhat it is
eventyesevent type from the table above
projectIdyesproject ID; needed even in a request with a key
eventIdyesyour event number: 8 to 64 characters, only Latin letters, digits and . _ ~ -
guidnobuyer identifier, if you have it (where it comes from)
emailnocustomer email

The identifier and email are optional, but send everything you know: the ad network recognizes the person by them.

The answer is 204 without a body. If there is nowhere to keep the key at this point, sign the body with a secret: the procedure is in Authentication.

How not to duplicate an event

We drop a repeat with the same event number in the same project and answer the same way as to the first one.

Take the number from your own record instead of creating a new one for each attempt: the store transaction number, the order number, the user number plus the event type. A random number on each retry after a network failure turns one purchase into several conversions.

Send a corrected event under a new number. A repeat is dropped entirely, even with a different body: a field added under the old number will not arrive.

Our library in the app and the attribution tracker can also tell us about an install. If we recognized the customer, by the guid identifier or by an email they verified with us, all reports merge into one install, and it gets into the project owner's report. Without that there is nothing to merge with: each report becomes its own conversion, and the report has no install.

We pass data about the person to the ad network: email, phone, IP address, click ids. This needs a basis: the customer's tracking consent. One of two will do:

  1. Our record. We find it only if we recognized the customer, by identifier or verified email. It counts when the person went through your funnel in the browser themselves and did not refuse tracking on the banner, and a visitor from Europe consented explicitly.
  2. Your declaration: the consent field in the event body, "granted" or "denied".

Neither one, and the conversion does not go to the network. We still answer with success, your server learns nothing, and the ad dashboard misses a sale. A customer who got into the app bypassing the funnel has no record with us: only your field decides for them.

Sending is visible in the cabinet: Project settings → "Analytics and pixels" → "Ad sources", the "Delivery log" button at a connected network. A "Decided not to send" row with a consent reason, for an event without your declaration, means no basis was found.

Fields for ad networks and the partner

All are optional: without them we accept the event, but the network gets less.

ToSend
send the purchase to the network with a valueamountCents and currency together; currency is a three-letter ISO 4217 code, any case: usd and USD are equal
let the network recognize the personphone with the country code, for example +15551234567: the network will not recognize a number without it; for Meta fbc and fbp, the values of the _fbc and _fbp cookies from the customer's browser; for TikTok ttp, the _ttp cookie, and ttclid, the click id from the address they came by; clientIp and clientUserAgent, the address and browser of the customer, not of your server
let the partner attribute the sale to a clickclickId, transactionId; without a deal number the partner gets your eventId instead
put the conversion on its own dayeventTimeSec
give TikTok the page address (a required field for it)eventSourceUrl: only a real address; if you have none, do not send the field
keep a test purchase from going to the network or the partnerlivemode: false

The amount is in the minor units of the currency, despite the word "cents" in the field name: 19.99 USD is 1999, and 500 yen is 500, since yen and won have no subdivision. Send the currency together with the amount. Without it the purchase goes to the network without a value, and an amount in yen ends up a hundred times smaller at the partner. The amount and deal number work only for purchase.

Event time is unix time in seconds, not milliseconds. You need it if you collect events and send them in a batch: otherwise the network assigns the purchase to the delivery day. Networks do not take events older than seven days, so we move a time older than "six days and eighteen hours ago" to that mark; six hours are left for the trip to the network. A time later than an hour from now becomes "in an hour". Milliseconds get the refusal 400.

We drop unknown fields without a word. A typo in a name, for example amount instead of amountCents, gives no refusal, and the amount is lost.

An event without a single sign of the person is not sent to a network. The signs are identifier, email, phone, click ids, the customer's address and browser. In the send log of each connected network such an event leaves the row "there is nothing to recognise the visitor by". The full field list with length limits is in the Reference.

Test events

The easiest way to debug is with a test key sk_test_…: an event with it is a test one if the body has no livemode field. Sending with a live key or signing the body with a secret? Mark a test purchase with the flag from the table above. The field value in the body beats the key mode.

The flag stops both ad networks and the postback to the partner's tracker.

A test event is not checked for consent: it is filtered out before that check, and the send log will have the row "a test purchase". Check the first live event against the log separately.

Refusals

AnswerWhen it comes
204the event was accepted, or it is a repeat of an accepted one
400 with a code fieldthe event was not accepted; fix the body and repeat with the same number: event_time_in_milliseconds, the time is in milliseconds; invalid_currency, the currency is not in the ISO 4217 list; invalid_client_ip, clientIp is not an IP address
400 without a codethe body was not parsed: a required field is missing, event is not one of the three, projectId is not a UUID, the event number, identifier or email has the wrong format. The answer does not say which field is at fault
404unknown key, the signature did not match or is stale, the project's plan is not paid: one answer for all, and the cause cannot be told from it. Check in order: key or signature (a signature timestamp in seconds instead of milliseconds also gives 404), then the project's plan with the owner
429 with a code fieldrate ceiling exceeded: PUBLIC_EVENT_PROJECT_RATE_LIMITED, more than 300 events a minute per project; PUBLIC_EVENT_FAILED_AUTH_RATE_LIMITED, more than 60 requests a minute from one address without a valid key or signature; PUBLIC_EVENT_IP_RATE_LIMITED, more than 3000 requests of any kind a minute from one address. Wait as many seconds as the Retry-After header says and repeat with the same number
500we did not record the event: repeat with the same number

In short

Next