Event sources count conversions and report health. A campaign can name a source in an event
optimization goal: when that campaign buys on a seller,
Semicola registers the source on the seller’s account and forwards later events to it. Conversions
don’t feed Semicola’s own reporting or attribution.
In the app
Open Event sources for an advertiser (or ask Semi, which callsopen_page with
page: "event_sources"). Add event source asks for:
Selecting a source shows Log an event from your server, a server-side snippet for that source, and
Send test event, which sends one event as test traffic (
testEventCode): it’s validated like any
event but not counted, so the source’s health and counts don’t change.
Event types
Health
Each source also reports
lastEventAt, eventsLast24h and eventsLast7d.
How a source reaches each seller
Register a source once, on the advertiser, never per seller. When a campaign whose optimization goal names the source sends a buy to a seller, Semicola registers the source on that seller’s account with AdCPsync_event_sources, as part of
sending the buy. That step waits at most 5 seconds (shared with forwarding the campaign’s
audiences) and never holds the buy back. The seller answers with its
own id for the source, and every later buy with that seller sends the seller’s id in the goal, not
yours.
If the seller hasn’t returned an id, the buy goes ahead without that one goal: Semicola never sends a
source id the seller was never given. The campaign’s other goals still go.
Each source lists where it has been registered in sellerProvisioning, one entry per seller account:
An entry also carries
storefrontId (the seller), sellerAccountId (the seller account the source
was registered on: the mapped ads account for a platform seller, otherwise the advertiser’s brand
domain) and lastSyncedAt. Every source is managedBy: "buyer" with sellerId: null.
Turning Events off for a seller in Connections stops all of
this for that seller: no registration, no forwarded events, and event goals are left out of its buys.
From an agent
Over MCP, find sources withsearch (kind: "measurement_source", filter.advertiserId, and a
query matched against the name, eventSourceId and integration platform) and read one with get,
passing its id and the advertiserId. The id is event: followed by the eventSourceId; campaign
goals use the plain eventSourceId. Archived sources aren’t listed. Create or
update one with save_measurement_source (sourceType: "event"), and log events with log_event
(the same fields as the REST call below). An agent can also send the person to the Event sources
page. See the Set up an event source skill.
REST
All paths are underhttps://api.semicola.com/api/v2/buyer.
Create or update a source
{ "created": true, "source": { … } }; created is false when the key already
existed. integrationPlatform is an optional free-text label.
Sync several sources
action: created, updated or failed (with an error, for example when
event_types lists no supported type). delete_missing isn’t supported and is refused: a sync never
deletes sources.
Log events
rejected lists each refused event by index with a reason, for example “purchase is not enabled on
this source.” Logging to a source key that doesn’t exist returns NOT_FOUND.
Use eventId for idempotency. An event whose eventId was already logged for the same event type
on the same source, earlier in the batch or in an earlier call, is dropped and counted in duplicates
instead of accepted. Events without an eventId are always counted, so a resent one counts twice.
A rejected event (a type that isn’t enabled, or a time more than an hour ahead) doesn’t use up its id.
log_event over MCP behaves the same way.
Test traffic. Add testEventCode to the call to mark it as test traffic: its events are
validated and reported in accepted, rejected and duplicates like any others, but they aren’t
counted in the source’s totals or summaries, don’t change its health or last event, and don’t use up
their eventIds. Sellers receive them with test_event_code, so they validate them without counting
them either.
Custom events. An event with eventType: "custom" needs a customEventName; it’s forwarded to
sellers as custom_event_name.
Forwarding to sellers. Accepted events that carry an eventId are forwarded with AdCP
log_event to every seller the source is provisioned on whose Events switch is on (unless
you’ve turned the source’s syndication to that seller off), under the seller’s own id for the
source; value and currency travel as custom_data. Events without an eventId are counted here
but never forwarded, because sellers deduplicate on it. Duplicates and rejected events aren’t
forwarded. Every seller is sent the batch at once, and the log call waits at most 5 seconds for all
of them together; it doesn’t report each seller’s result. A seller that doesn’t answer in time
misses that batch (resending it is safe: sellers deduplicate on eventId).
What Semicola keeps is a daily count and total value, and an hourly count, per event type, plus the
event ids it has seen. It doesn’t store individual events or any identity fields, so the log call takes
no user-match data (hashed emails, click ids, device ids).
Hourly summary
GET /api/v2/buyer/advertisers/{advertiserId}/events/summary returns hourly counts for the advertiser’s
sources: optional eventType, startHour and endHour (ISO 8601; hours are whole UTC hours, end
exclusive). Without a window it covers the last 24 hours.
custom_data, match quality, seller-managed sources, and
pre-aggregated measurement data uploads.