Events and users
Revenue from your server
Send purchases, renewals and refunds from your backend with your server key.
Send purchases, subscription renewals and refunds from your backend. This is the revenue that counts for creators: it can't be faked from a phone, and it arrives even when the purchase happened on your website or the store renewed a subscription while the app was closed.
Keep your server key on your server
Registering an app gives it a server key, shown once. Store the token in your secret manager and read it from the environment. Lost it? Create a new one in Settings → SDK keys → Create key → Server key.
.env DROPPLINK_SERVER_KEY=sk_5a7c9e1b3d2f4a60.your_secretNever put it in an app, a web page or a repository. See SDK and server keys.
Identify users in your app
Your app calls
identifywith your user ID when someone logs in. That's how a purchase from your server finds the creator who brought the user.Send purchases from your payment webhook
When your payment provider confirms a payment, send it with that user's ID:
Node.js await fetch("https://attribution.ondropp.app/v1/s2s/events", { method: "POST", headers: { Authorization: `Bearer ${process.env.DROPPLINK_SERVER_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ events: [{ eventId: `payment-${payment.id}`, // your own ID: a repeat webhook counts once userId: payment.userId, // the ID your app passes to identify name: "purchase", ts: Math.floor(payment.paidAt.getTime() / 1000), // unix seconds revenue: { amount: payment.amount, currency: payment.currency, orderId: payment.orderId }, }], }), });Python import os, requests requests.post( "https://attribution.ondropp.app/v1/s2s/events", headers={"Authorization": f"Bearer {os.environ['DROPPLINK_SERVER_KEY']}"}, json={"events": [{ "eventId": f"payment-{payment.id}", "userId": payment.user_id, "name": "purchase", "ts": int(payment.paid_at.timestamp()), "revenue": {"amount": payment.amount, "currency": payment.currency, "orderId": payment.order_id}, }]}, timeout=10, )curl curl -X POST https://attribution.ondropp.app/v1/s2s/events \ -H "Authorization: Bearer $DROPPLINK_SERVER_KEY" \ -H "Content-Type: application/json" \ -d '{ "events": [{ "eventId": "payment-pay_123", "userId": "user_8812", "name": "purchase", "ts": '"$(date +%s)"', "revenue": { "amount": 499, "currency": "INR", "orderId": "A1023" } }] }'Send refunds
A negative amount with the refunded order's
orderId. It's subtracted from the creator who got the purchase.Request body { "events": [{ "eventId": "refund-rfnd_456", "userId": "user_8812", "name": "refund", "ts": 1760700400, "revenue": { "amount": -499, "currency": "INR", "orderId": "A1023" } }] }Check it arrived
Paste the test phone's install ID into Install debugger. The purchase shows under Latest events, marked Server.
Use it well#
- Make
eventIdyour own stable ID: the payment ID, order ID or invoice ID. Payment webhooks often fire twice; the sameeventIdwithin 8 days is stored once, so retrying is always safe. - Send
tsas when it happened, in unix seconds, within the last 7 days. Milliseconds are rejected. - Send renewals as their own events (
subscription_renewed), each with its owneventId, so recurring revenue keeps going to the creator who brought the subscriber. - Batch when you can. One request takes up to 100 events. A nightly backfill or a queue worker should send them in batches.
- Retry on
429and5xx. Wait for theRetry-Afterheader (seconds), then send the same request again. A503means nothing from it was kept. - Don't send the same purchase from the app as well. If your app also calls
track("purchase")with revenue, it's shown separately as app-reported and doesn't double the server total, but it's clearer to keep revenue on the server.
If the app hasn't identified the user yet#
Webhooks often arrive before the app's identify, for example when someone pays on your website first. That's fine: the event is
kept as pending and credited as soon as the user is identified. If they never are, it's stored as organic after 7 days. It's
never dropped. The response tells you how many were pending:
{ "accepted": 1, "duplicates": 0, "pending": 1, "rejected": [], "warnings": [] }You can also send an installId instead of a userId, for an event tied to one install, such as an in-app purchase you validated
on your server before the user signed up.
Orders placed on your website#
With the web script on your site, send the order's visitorId (the dropp_vid cookie): it's credited to the
Dropplink link that first brought that visitor, when the customer has no creator yet. Without the script, send the dropp_click_id
the visitor landed with as clickId. Either way, with a userId the click becomes that customer's first touch, so their later
orders need only the userId.
Full request, response and error details: Server API reference.