Returning.AIDevelopers
v1

Guides / Broker Integrations

.md

Referrals

Referral attribution has separate checkpoints: capture the incoming value, validate it on the server, enroll the new user with the referrer value, read the relationship back, and then check configured qualification and reward state. The referral value never chooses the identity of the authenticated widget.

Referral attribution to authenticated display

Attribution input, relationship state, qualification, rewards, and widget identity are separate.

  1. Capture the incoming value

    Browser

    Read the configured query parameter before redirects or registration, and carry it as onboarding input. It is not a widget identity.

  2. Validate and enroll

    Broker server

    Resolve the value against the configured referral mapping, then create CUSTOMER_1001 with CUSTOMER_0900 in referral only when policy validation passes.

  3. Read the relationship

    Broker server

    Read the new user by email or returned platform ID, then read the referrer summary with the same identifier namespace used by the program.

  4. Check qualification and rewards

    Returning.AI configuration

    Send the real configured business state through its approved field, event, or workflow path. Relationship presence is not stage completion or a reward balance update.

  5. Mint the widget token from the trusted CUSTOMER_1001 session and mount the supplied referral bundle. Never sign the query value as the logged-in user.

Discover the configured programs

Use GET https://api.returning.ai/v1/referral/programs?lang=en with the server-side community API key and getUserData permission. This is a read-only configuration call. Its program IDs, stage names, condition keys, and reward amounts belong to the community; do not hard-code the placeholders below.

referral-programs.sh
export RAI_API_BASE="https://api.returning.ai/v1"
export COMMUNITY_API_KEY="<COMMUNITY_API_KEY>" # server-side only

curl --fail-with-body --request GET \
  "$RAI_API_BASE/referral/programs?lang=en" \
  --header "Authorization: Bearer $COMMUNITY_API_KEY" \
  --header "Accept: application/json"

A valid response can contain an empty data array when no program is enabled. Save the configured program and stage details for launch checks rather than treating discovery as proof that a member has qualified.

referral-programs-response.json
{
  "meta": {
    "status": "success",
    "statusCode": 200
  },
  "data": [
    {
      "enabled": true,
      "programId": "<PROGRAM_ID>",
      "name": "<CONFIGURED_PROGRAM_NAME>",
      "stages": [
        {
          "stageId": "<STAGE_ID>",
          "stageNumber": 1,
          "name": "<CONFIGURED_STAGE_NAME>",
          "mainConditions": [],
          "rewards": {
            "xp": 0,
            "coins": 0
          }
        }
      ]
    }
  ]
}

Capture before registration

Capture the query value before a redirect or registration form discards it. The parameter name is an integration setting, so replace <REFERRAL_QUERY_PARAM> with the configured name. The browser can carry this value to your server, but the server must validate the allowed namespace, current program policy, and referrer mapping before placing it in a create request.

capture-referral.js
// Browser-only capture. The parameter name is configured by your integration.
const referralValue = new URLSearchParams(window.location.search)
  .get("<REFERRAL_QUERY_PARAM>");

// Keep this as onboarding input. Do not use it as the signed-in widget identity.
if (referralValue && !sessionStorage.getItem("pendingReferral")) {
  sessionStorage.setItem("pendingReferral", referralValue);
}

This example keeps the first value in this tab and origin only. Clear it when signup completes or is cancelled. If onboarding crosses origins, tabs, or sessions, carry the validated attribution in your server-side onboarding record under the agreed retention policy.

The query value is not authentication

Never use the referral query value to decide which user receives a signed widget token. Resolve the host session independently, then sign the registered user's configured identifier on the server.

Create the referee with validated attribution

After server validation, create the new community user through the public /v1/users route. The community key needs createUser. The request below uses the active identifier example; use the configured custom-field form instead when that is the community's registration mapping.

create-referee.sh
export RAI_API_BASE="https://api.returning.ai/v1"
export COMMUNITY_API_KEY="<COMMUNITY_API_KEY>" # server-side only

curl --fail-with-body --request POST "$RAI_API_BASE/users" \
  --header "Authorization: Bearer $COMMUNITY_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "firstname": "Referee",
    "lastname": "Example",
    "username": "customer_1001",
    "email": "customer-1001@example.com",
    "accessLevel": 1,
    "joinServer": true,
    "sendEmail": false,
    "emailPassword": false,
    "externalId": "CUSTOMER_1001",
    "referral": "CUSTOMER_0900"
  }'

Read the create result and then read the new user back by its known email or returned platform ID. If the create request times out or returns a 5xx, use bounded readback before any resubmission. If the user was created but referral association is delayed or missing, keep the user and reconcile the relationship; never create the referee again.

Read the referrer summary

Use POST https://api.returning.ai/v1/referral/summary with the community key and getUserData. The identifier type must match the configured namespace. The supported types are id, email, data-email, data-customer-id, cognito-sub, and custom-field. For custom-field, send the exact configured fieldName; do not guess a field or treat a custom-field lookup on a different endpoint as proof that it works here.

referral-summary.sh
export RAI_API_BASE="https://api.returning.ai/v1"
export COMMUNITY_API_KEY="<COMMUNITY_API_KEY>" # server-side only

curl --fail-with-body --request POST "$RAI_API_BASE/referral/summary" \
  --header "Authorization: Bearer $COMMUNITY_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "identifier": {
      "type": "data-customer-id",
      "value": "CUSTOMER_0900"
    },
    "pagination": {
      "limit": 20
    }
  }'

The custom-field variant is a request body change, not a second identity value. Use it only when the community owner confirms the field mapping.

custom-field-summary-body.json
{
  "identifier": {
    "type": "custom-field",
    "fieldName": "customer_id",
    "value": "CUSTOMER_0900"
  },
  "pagination": {
    "limit": 20
  }
}

Set pagination.limit between 1 and 100. If the response returns anextCursor, send that exact opaque value in the next request'spagination.cursor. Do not decode it, manufacture it, or reuse it with another user, program, community, or key.

referral-summary-response.json
{
  "data": {
    "user": {
      "userId": "<REFERRER_PLATFORM_USER_ID>",
      "email": "customer-0900@example.com"
    },
    "invite": {
      "url": "https://portal.example.com/join?ref=CUSTOMER_0900"
    },
    "activity": {
      "totalReferrals": 1,
      "inProgress": 1,
      "completed": 0
    },
    "referees": [
      {
        "userId": "<REFEREE_PLATFORM_USER_ID>",
        "username": "customer_1001",
        "progressByProgram": []
      }
    ],
    "pagination": {
      "hasNextPage": false,
      "nextCursor": null
    }
  }
}

When the response includes data.invite.url, render that returned URL. Do not manufacture a platform URL from an invite code or substitute platformUrl when the configured invite.url is present. A valid member with no referrals can return 200, zero counters, and an empty referees array.

Keep relationship, qualification, and reward separate

StateEvidenceDo not infer
Attribution inputThe browser captured a configured query value and the server accepted it after policy validation.That the value identifies the signed-in widget user or is a valid referrer without mapping checks.
RelationshipThe referrer summary contains the new referee or the configured relationship readback shows the pair.That a stage condition has completed or a reward has been credited.
QualificationThe configured field, event, or workflow state has been saved and its evaluation readback is complete.That every field write maps to a milestone or that evaluation is immediate.
RewardThe configured reward or ledger readback confirms the award. The Returning.AI owner supplies that readback operation.That a completed stage or its configured reward amount proves a balance was credited, or guarantees retroactive credit or timing.

For a field-backed condition, follow the field lifecycle and read the saved value before polling referral progress. Do not replay a field write just because one summary read is still zero.

Retry reads, reconcile writes

Programs and referral summaries are read-only surfaces. A bounded retry can help with a transient 5xx or upstream failure, while 400 responses call for correcting the identifier, field name, or pagination and 401/403 responses call for fixing the server key or its permission. A 404 user result calls for checking the environment, namespace, and exact value.

Mount the current referral widget

The referral widget uses the same custom-widget and Access Key Embed flow as other configured experiences. The token request is server-only and signs the referee's own configured identity, here CUSTOMER_1001. The browser receives the short-lived embed token, never the server credentials. Signed token contents are browser-visible; do not put secrets or unnecessary personal data inside them.

mint-referral-widget-token.sh
# Server-side example. Replace placeholders or use the authenticated starter.
curl --fail-with-body --request POST \
  "https://api-v2.returning.ai/v2/api/widget-access-keys/token" \
  --header "Content-Type: application/json" \
  --data '{
    "accessId": "<RAI_ACCESS_ID>",
    "accessKey": "<RAI_ACCESS_KEY>",
    "userIdentifiers": {
      "data-customer-id": "CUSTOMER_1001"
    }
  }'
referrals.html
<script src="https://unpkg.com/@returningai/widget-sdk@1.8.11/dist/rai-widget.iife.js"></script>

<rai-custom-widget
  widget-id="<WIDGET_ID>"
  bundle-url="<SUPPLIED_BUNDLE_URL>"
  embed-token="<SHORT_LIVED_JWT>"
  height="auto"
></rai-custom-widget>

Keep the supplied widget-id and bundle-url pair together. On token expiry, fetch a fresh token from the server and update embed-token on the existing element. Removing the element on logout is browser cleanup; it is not proof that an already issued token was revoked.

Continue to launch checks

Open the launch checklist for the environment mapping, relationship and qualification cases, named owners, and pause/replay rules. If enrollment itself is not settled, return to Enroll traders before testing referral progress.

Operation references

Use the current read-only operation pages for referral programs and referral summary. For enrollment details, see create a user. These pages document the operation shapes; program configuration and identity mapping still belong to the community setup owner.