Send first-party events
Use the SpendDaddy API when your backend sends subscription and attribution events directly instead of using RevenueCat.
Before you start
Choose SpendDaddy API while adding your app, then copy the generated API key into a server-side secret store.
Never embed this key in a mobile app or other downloadable client. Anyone with the key can send events for the connected app.
All requests use:
Authorization: Bearer <spenddaddy_api_key>
Content-Type: application/json
The base URL is https://api.spenddaddy.app.
Identify an install
Call POST https://api.spenddaddy.app/identify when your backend first sees an app user. Send the same stable user ID you will use for later events.
curl https://api.spenddaddy.app/identify \
--request POST \
--header "Authorization: Bearer $SPENDDADDY_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"appUserId": "user_123",
"installedAt": "2026-07-17T12:34:56Z",
"asaToken": "optional_apple_ads_attribution_token"
}'
| Field | Required | Description |
|---|---|---|
appUserId | Yes | Stable identifier for this app user. |
installedAt | No | Install time in ISO 8601 format. Defaults to the time SpendDaddy first sees the user. |
asaToken | No | Apple Ads attribution token obtained by your app and forwarded to your backend. |
Example response:
{
"status": "identified",
"created": true,
"attribution": "fetched"
}
created is true when SpendDaddy created the app user. attribution is fetched, failed, or not_requested depending on whether an Apple Ads token was supplied and resolved.
Apple can take a few seconds to make attribution available after install. SpendDaddy retries briefly; if the response still says failed, call /identify again later with the same token. Tokens stay valid for 24 hours. Sending the token gives SpendDaddy Apple's claim type, so it can tell tap-through installs from view-through ones.
Send an event
Call POST https://api.spenddaddy.app/event for trials, purchases, renewals, refunds, and cancellations.
curl https://api.spenddaddy.app/event \
--request POST \
--header "Authorization: Bearer $SPENDDADDY_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"appUserId": "user_123",
"eventType": "initial_purchase",
"occurredAt": "2026-07-17T12:40:00Z",
"eventId": "evt_01",
"transactionId": "txn_01",
"productId": "monthly_pro",
"amount": 9.99,
"currency": "USD",
"revenueBasis": "net_proceeds",
"store": "app_store",
"country": "US"
}'
Event fields
| Field | Required | Description |
|---|---|---|
appUserId | Yes | The same stable user ID used with /identify. |
eventType | Yes | One of the supported event types below. |
occurredAt | Yes | Event time in ISO 8601 format. |
eventId | Required for explicit revenue unless transactionId is set | Stable identifier. Resending the same ID updates the stored event instead of adding a new one. |
transactionId | Required for explicit revenue unless eventId is set | Store transaction identifier. |
renewalNumber | No | Renewal sequence number when available. |
productId | No | Subscription or product identifier. |
amount | Required for revenue events | Positive revenue amount. Send refunds as a positive amount; SpendDaddy applies the negative sign. |
currency | Required with revenueBasis for revenue events | Three-letter currency code. Legacy calls without a basis default to USD when omitted. |
revenueBasis | Recommended for revenue events | net_proceeds means amount is already after tax and store commission. gross means it is before those fees. |
taxPercentage, commissionPercentage | For gross proceeds calculation | Fractions from 0 to 1; both are needed to calculate proceeds. If either is missing, gross is stored but net proceeds remain unknown. Do not send fees with net_proceeds. |
store | No | Store that processed the event, such as app_store. |
country | No | Two-letter country code when available. |
SpendDaddy still accepts calls without revenueBasis and stores them as legacy_assumed_net, meaning the amount is assumed to be net but not verified. Send a stable eventId or transactionId with explicit revenue so corrections update the same event. Refund amounts are positive in the request and stored as negative revenue/proceeds.
Supported event types
| Event type | Requires amount |
|---|---|
trial_started | No |
trial_converted | Yes |
initial_purchase | Yes |
non_renewing_purchase | Yes |
renewal | Yes |
refund | Yes |
cancellation | No |
Example response:
{
"status": "ingested",
"created": false
}
Here, created says whether SpendDaddy created the app user while processing the event. It does not say whether the event was new.
Retries and duplicate events
Provide a stable eventId or transactionId whenever possible. If a request times out, retry it with the same identifiers and payload so SpendDaddy can recognize the existing event.
Errors
| Status | Meaning | What to do |
|---|---|---|
400 | Unsupported event type, missing revenue amount, or invalid amount. | Correct the request body and retry. |
401 | Missing, invalid, disabled, or rotated API key. | Confirm the bearer token, or rotate the key in Settings → API & webhooks. |
422 | A required field is missing or has an invalid format. | Compare the request with the field tables above. |
Rotate the API key
Open the app in SpendDaddy, go to Settings → API & webhooks, and select Rotate API key under First-party attribution API.
Rotation invalidates the previous key immediately. Update the server-side secret used by every event sender before sending more requests.