Deeplinkly

Glossary/Attribution mechanics

Server-to-server attribution

Definition

Server-to-server attribution is the practice of sending install and conversion events to an attribution endpoint from your own backend rather than from the device SDK, using an identifier captured at click time to join them to the campaign.

The device is a hostile reporting environment: apps get killed, networks drop, and any payload a client sends can be forged. S2S moves the reporting boundary to a place you control, which buys you reliability and trustworthiness at the cost of having to carry the click identifier yourself. It does not replace the SDK — something still has to observe the install.

The three hops, and where the identifier lives

S2S attribution is a join across three events that happen on three different machines. The only thing making it a join rather than a guess is that a single opaque identifier — the click ID — is carried from the first hop to the third.

Where the click identifier lives at each stage of an S2S flow.
HopHappens onIdentifier carrierFails when
ClickYour link serverGenerated and storedNever — you own this
Store visitApp Store / PlayAndroid: install referrer. iOS: not carriediOS drops URL parameters here
First openThe deviceSDK reads referrer or matches deferredApp opened without ever seeing the link
Install eventYour backendClick ID sent in the payloadClient never forwarded it
Later conversionsYour backendYour own user ID, joined to the click IDUser ID assigned before click ID stored

The row that surprises people is the last one. If a user registers before your backend has seen the click ID, the join is broken forever unless you write the click ID onto the user record at the moment the two are first known together. Storing it only on the session is the single most common cause of conversions that attribute to nothing while installs attribute fine.

The actual request

An S2S install postback is an ordinary signed HTTP request. There is no magic in the format — what makes it trustworthy is the signature, the timestamp and the idempotency key, all of which are absent from most client-sent equivalents.

An install postback sent from your backend
POST /v1/events/install HTTP/1.1
Host: api.example.com
Content-Type: application/json
X-Signature: sha256=8f4c2b91ad0e5c7f1b3a9d6e2f8c0a4b7d1e3f5a9c2b8d4e6f0a1c3b5d7e9f2a
X-Signature-Timestamp: 1755532800
Idempotency-Key: install-6aafb7a5-0170-41b5-bbe4-fe71dedf1e28

{
  "event": "install",
  "click_id": "clk_01J9Z3QK7M2N4P6R8T0V",
  "app_id": "com.example.app",
  "platform": "android",
  "occurred_at": "2026-08-18T14:22:31Z",
  "install_referrer": "utm_source=newsletter&utm_campaign=august&clk=clk_01J9Z3QK7M2N4P6R8T0V",
  "referrer_click_timestamp_seconds": 1755528000,
  "install_begin_timestamp_seconds": 1755531900,
  "device": {
    "os_version": "15",
    "advertising_id": null,
    "app_set_id": "d1f3a7c9-2b48-4e6a-9c1d-5f7b3e9a0c24"
  }
}
The response, including the attribution decision
HTTP/1.1 200 OK
Content-Type: application/json

{
  "event_id": "evt_01J9Z3RB4X8Y2Z6A0C4E",
  "deduplicated": false,
  "attribution": {
    "status": "attributed",
    "method": "deterministic",
    "click_id": "clk_01J9Z3QK7M2N4P6R8T0V",
    "campaign": "august-newsletter",
    "source": "newsletter",
    "click_to_install_seconds": 4531,
    "window_days": 7
  }
}

Two fields in the response are worth surfacing in your own logs. method tells you whether the match was deterministic or modelled — folding probabilistic matches into the same count as deterministic ones destroys the only quality signal you have. And click_to_install_seconds is the raw material for the CTIT distribution, which is how fraud shows up before it shows up in spend.

What S2S actually buys you

Client-sent events versus server-sent events on the properties that matter.
PropertyDevice SDKServer-to-server
Forgeable by a userYesNo — the key never ships
Survives app kill mid-requestOften notYes
Blocked by network conditionsFrequentlyRarely
Retryable with idempotencyHardStraightforward
Sees purchase and refund stateOnly what the client knowsAuthoritative
Sees the install itselfYesNo — needs the client to report it
Adds latency to your critical pathNoYes, if sent synchronously

The last two rows are why S2S is a complement rather than a replacement. Only the device knows the app was opened for the first time; only the server knows whether the payment cleared. A sensible split is that the SDK reports the install and session lifecycle, and the backend reports everything with money or entitlement attached — including refunds, which a client will never send you.

Sign the payload and verify the timestamp, or you have built an open endpoint

An unsigned S2S endpoint is worse than a client SDK, because it looks authoritative while accepting anything. Verify an HMAC over the raw body, reject signatures older than a few minutes to stop replay, and compare digests in constant time. The webhook page covers the same verification in the other direction.

Idempotency is not optional

Every retry policy that guarantees delivery guarantees duplicates. A network timeout after the server committed the write looks identical to a failure, so the sender retries, and the install is counted twice. The fix is a deterministic key on the sending side and a uniqueness constraint on the receiving side.

The receiving table, with duplicate suppression in the schema rather than the application
CREATE TABLE attribution_events (
  event_id        TEXT PRIMARY KEY,
  idempotency_key TEXT NOT NULL,
  click_id        TEXT,
  app_id          TEXT NOT NULL,
  event_name      TEXT NOT NULL,
  occurred_at     TIMESTAMPTZ NOT NULL,
  received_at     TIMESTAMPTZ NOT NULL DEFAULT now(),
  payload         JSONB NOT NULL,
  -- Two deliveries of the same event can never both land.
  CONSTRAINT attribution_events_idem UNIQUE (app_id, idempotency_key)
);

-- Retries become no-ops instead of double counts.
INSERT INTO attribution_events (event_id, idempotency_key, click_id, app_id,
                                event_name, occurred_at, payload)
VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (app_id, idempotency_key) DO NOTHING;
  • Derive the key from the event, not from the attempt — a UUID generated per request defeats the purpose.
  • Send asynchronously from a queue, so a slow endpoint never delays a user's request.
  • Retry with exponential backoff and a cap; a stuck queue that retries forever is an outage amplifier.
  • Log the response attribution.status, not just the HTTP code. A 200 that says unattributed is still a 200.
  • Keep clocks synchronised. Timestamp-based signature rejection fails confusingly on a drifting host.

UTM builder

An S2S join is only as good as the identifier the click carried. This builds correctly-encoded campaign URLs with a live preview, which is also the fastest way to catch the unencoded ampersand that silently truncates an Android install referrer before your backend ever sees the click ID.

Open the utm builder

Frequently asked questions

What is server-to-server attribution?
It is sending install and conversion events to an attribution endpoint from your own backend rather than from the device, joining them to a campaign using a click identifier captured when the user first tapped the link. The device SDK still reports the install itself; the server takes over for events where reliability and authenticity matter.
How does the click ID reach the server?
On Android it survives the store through the Play install referrer, which the SDK reads on first open and forwards to your backend. On iOS nothing survives the store boundary, so the click ID must be recovered by a deferred deep-link match at first open. Once your backend has it, write it onto the user record so later conversions can still join.
Is server-to-server attribution more accurate than SDK attribution?
It is more trustworthy rather than more accurate. A server-sent event cannot be forged by a user, cannot be lost to an app being killed mid-request, and can be retried safely with an idempotency key. Accuracy still depends entirely on whether the click identifier survived the journey, which is a link problem rather than a transport problem.
Do I still need a mobile SDK if I use S2S?
Yes, for the install itself. Only the device can observe a first app open, read the Play install referrer, or resolve a deferred deep link, and your server has no way to learn those things independently. The usual division is that the SDK reports install and session lifecycle while the backend reports purchases, refunds and anything tied to entitlement.
How do I stop duplicate events from retries?
Send a deterministic idempotency key derived from the event itself, never from the retry attempt, and enforce it with a uniqueness constraint on the receiving table so a repeated delivery becomes a no-op rather than a second install. Any delivery guarantee strong enough to survive timeouts will produce duplicates, so this belongs in the schema rather than in application code.

Related terms

  • WebhookA webhook is an HTTP request a service sends to a URL you control when an event occurs, delivering the event's data without your system having to poll for it.
  • Deterministic attributionDeterministic attribution credits an install to a specific click by matching an identifier that is present in both records, producing a one-to-one link rather than a statistical estimate.
  • Play Install ReferrerThe Play Install Referrer is a Google Play API that lets a newly installed Android app read the referrer string and click timestamps recorded when the user arrived at its Play Store listing.
  • Click-to-install timeClick-to-install time is the elapsed time between the click on an ad and the first open of the installed app, measured per install and analysed as a distribution rather than an average.