Product
Publishers
Advertisers
Networks
Help
Pause animations

Setup without an SDK

Send events from your server. No SDK.

One server-to-server POST for each step a user completes. Test every event for free before you spend anything.

Test events are free. Verify each required event before you go live.

curl -X POST "$AFOS_API/v1/advertiser/events" \
  -H "Authorization: Bearer $AFOS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "clickId": "clk_7Q2f81",
    "advertiserUserId": "u_48213",
    "eventId": "test-u_48213-level_10",
    "timestamp": "2026-10-29T19:06:00Z",
    "appId": "app_harbor_blocks",
    "eventKey": "level_10",
    "test": true
  }'
Response · 2:06:00 PM Verified

{ "status": "approved",
"payoutUsd": 5, "logId": "…" }

payoutUsd is the step's price, not a charge.

  1. Request received 2:06:00 PM
  2. API key checked 2:06:00 PM
  3. Request received, API key checked 2:06:00 PM
  4. Logged as a test event, $0.00 chargedLogged as a test, $0.00 charged 2:06:00 PM
  5. level_10 marked Verified 2:06:01 PM
API key and host
An ak_ key you mint in the campaign, kept on your server. AFOS_API is the host in the event docs.
Click id
Saved on the user when your app first opens or your page loads.
Event keys
One per step, such as level_10. The dashboard suggests them.

01Responses

Every answer, and whether it charged.

Each request with a valid key gets one of these statuses back. Only a live approved event is charged.

Retries are safe. Send the same eventId again and the answer is duplicate, never a second charge. Give test events their own eventId: a live event that reuses a test's id also comes back duplicate, and isn't charged.

Every status a request with a valid API key gets back from the advertiser events endpoint, and whether the event was charged
Status What it means Charged
approved live A live event with one of our clicks behind it, inside its window. Charged at your price for that step. Yes
approved test A test event. Marks the step Verified in your dashboard. Tests are never charged. No
duplicate The same eventId or transactionId arrived again, or this user already reached this step as many times as it pays (once by default). Safe to retry with the same id. No
missing_attribution A live event with no click id we can find. Logged, never charged. No
outside_window The event's timestamp is more than 48 hours from now, or the event falls outside that step's window, if it has one. No
campaign_paused The campaign was paused when the event arrived. No
insufficient_balance Your balance couldn't cover the step, so your live campaigns paused. Add funds, resume the campaign, then send the same event again within 48 hours of its timestamp and it can still charge. No
invalid_event The appId, eventKey or campaign doesn't match one of yours. No
rejected The campaign isn't live, the timestamp can't be read or the click belongs to another install or campaign. Also a purchase or deposit missing its transactionId or required revenue. No

A bad API key gets HTTP 401. A missing required field, or more than 120 requests a minute, gets HTTP 400. Neither is logged or charged.

02Install matching

Match the install three ways, none with an SDK.

Every paid event needs one of our click ids. These are the ways it reaches you. Pick the one that fits your app; you can use more than one.

  1. 01 Android

    Play install referrer

    Google Play hands our click id to your app in the install referrer on first open. Read it and save it on the user.

    Exact
  2. 02 iOS or Android

    First-open match

    When a new user first opens your app, your server calls us once with the device's IP address. We match the open to a recent click for the same app and platform from that address.

    Confidence shown
  3. 03 If you use one

    Your attribution tool

    When you add your app, enter your tool's tracking link as its App Store or Google Play URL. We add afos_click_id to it, so your tool can carry it into its install data. Forward the events your tool already sends your server with that click id, and map its event names to your event keys.

    No new tracking

Websites: we add afos_click_id to your landing page URL. Save it when the page loads and send it back with each event.

How a first-open match is decided

We check each recent click from that address against your app, the platform, the time since the click, whether another install already claimed it and whether our checks blocked it. If more than one person's click fits, we don't guess. Every decision shows on your Attribution page, with the checks each click passed and, on a match, its confidence.

Harbor Blocks: matched the only eligible click

First open 2:14 PM iOS IP 203.0.113.24

92 confidence
Clicks checked against one first open (Example)
Click AppPlatformWindowUnclaimedNot blocked Result
clk_7Q2f81 2:06 PM (8 min before) App: passed Platform: passed Window: passed Unclaimed: passed Not blocked: passed Eligible
clk_31b0c2 Yesterday, 9:40 PM App: passed Platform: passed Window: failed Unclaimed: passed Not blocked: passed Too old

03AI brief

Hand the setup to your coding assistant.

A plain-text brief with the rules your developer, or their AI assistant, needs to wire this up.

  • Where the click id comes from on the web, on Google Play and on iOS
  • The request, its fields and the API host
  • Safe retries, the 48-hour rule, purchases and when to turn test off

Inside a campaign, the dashboard has its own AI playbook with your app and campaign IDs filled in.

Your first event can be a test.

Mint an API key in your campaign and send the request above with test set to true.

Test events are free. Verify each required event before you go live.