DropplinkDocsPowered bydropp.

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.

  1. 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_secret

    Never put it in an app, a web page or a repository. See SDK and server keys.

  2. Identify users in your app

    Your app calls identify with your user ID when someone logs in. That's how a purchase from your server finds the creator who brought the user.

  3. 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" } }] }'
  4. 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" } }] }
  5. 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 eventId your own stable ID: the payment ID, order ID or invoice ID. Payment webhooks often fire twice; the same eventId within 8 days is stored once, so retrying is always safe.
  • Send ts as 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 own eventId, 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 429 and 5xx. Wait for the Retry-After header (seconds), then send the same request again. A 503 means 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:

200 OK
{ "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.

Something unclear? Tell us.Powered bydropp.