Walker Formulation Academy for developers API reference Error catalog OpenAPI
Sections

#Offline Conversions

Status: Planned. The POST /analytics/conversions method is being designed. This page records the agreed principles; the set of fields is provisional and will be finalized in OpenAPI 3.1.

Purpose: pass a payment or a won deal from your CRM and accounting system to Walker Formulation Academy, so that the platform links the money to a lead, a session, and an acquisition channel and calculates channel payback.

#Contents

  1. Why it is needed
  2. When to use it
  3. Access
  4. Request
  5. Linking to a lead, a session, or an external identifier
  6. Response
  7. Errors
  8. Examples
  9. How conversions reach the reports
  10. Notes
  11. Open questions

#Why it is needed

Site counters see visits and requests, but not money: payment happens in the CRM, at the till, or on the bank account. Without it, the Walker Formulation Academy strategist knows how many leads a channel brought but not how much it earned. An offline conversion adds the missing "lead → payment" link and makes it possible to calculate channel payback.

In Overview, the funnel steps deals_created, deals_won, and revenue are built from the platform's CRM data. If your deals live in an external CRM, this method and Leads and CRM are the way to give the platform the same information. The rules by which conversions and platform deals are merged without double counting are in How conversions reach the reports.

#When to use it

SituationWhat to choose
A payment or a won deal is recorded in your CRM or accountingthis method
The event happened in a browser or an app (a click, a sign-up)Events API
You need to update a lead's statusCRM statuses
You need amounts by channelReading analytics

#Access

Method and pathPOST https://<cell domain>/api/public/v1/analytics/conversions
AuthenticationAuthorization: Bearer <key> (Authentication)
Scopeanalytics:write
Paidno
Limit120 requests per minute per key and tenant (Conventions)
Personal datanot accepted. The analytics:write scope allows only pseudonymous identifiers: linking is by lead identifier, session, or your external identifier, not by email or phone. Contacts in properties are rejected with validation_failed
Idempotencythe Idempotency-Key header (Conventions) and the conversion_id field

Cell addresses: RU https://formula-cream.pro, EN https://wfacademy.org, ID https://wfacademy.id. The amount's currency matches the cell's market: RUB for RU, GBP for EN, IDR for ID (Cells and data).

#Request

Headers: Authorization, Content-Type: application/json, Idempotency-Key (Conventions).

Body (provisional)

FieldTypeRequiredConstraintsExample
conversion_idstringyesyour unique conversion identifier; a repeat does not create a second onepay_48213_1
typestringyespayment or deal_won (a won deal); the list will be finalizedpayment
valueobjectyes{"amount":"4990.00","currency":"RUB"}; the amount is a string, the currency is the cell's. A negative amount is a refund (see below){"amount":"4990.00","currency":"RUB"}
occurred_atstringyesISO 8601 UTC, the moment of payment2026-10-11T08:15:30Z
lead_idstringone of threethe lead identifier in Walker Formulation Academylead_EXAMPLE_01
external_idstringone of threethe identifier of the lead or deal in your CRM (the external_id field, see Leads)crm-48213
session_idstringone of threea session passed in the Events APIsess_EXAMPLE_01
propertiesobjectnoadditional key-to-scalar pairs, no personal data{"product":"emulsion-mastery"}

At least one of lead_id, external_id, session_id is required. If several are given, priority follows the order lead_id, external_id, session_id (provisional).

{
  "conversion_id": "pay_48213_1",
  "type": "payment",
  "value": { "amount": "4990.00", "currency": "RUB" },
  "occurred_at": "2026-10-11T08:15:30Z",
  "external_id": "crm-48213",
  "properties": { "product": "emulsion-mastery" }
}

#Linking to a lead, a session, or an external identifier

MethodWhen it fitsWhat the platform gets
lead_idthe lead was created or accepted through the leads APIa direct link to the lead and its attribution (source, UTM, visit history)
external_idthe lead was created by your CRM with POST /leads using the same external_ida link through your CRM's identifier
session_idthere is no request; the conversion comes from a visitor (for example, a purchase without a form)a link to the session and its channel

If the lead or session is not found, the conversion is rejected with the code not_found; the platform does not create "dangling" conversions.

#Response

Success is 201 Created for a new conversion and 200 OK on a repeat with the same conversion_id. The method is not asynchronous: no operation object is returned, and the reports are recalculated after the response.

{
  "conversion_id": "pay_48213_1",
  "status": "attributed",
  "lead_id": "lead_EXAMPLE_01",
  "attribution": { "channel": "search", "basis": "form" },
  "created_at": "2026-10-11T08:16:02Z"
}
FieldTypeDescription
conversion_idstringyour identifier
statusstringattributed: linked to a channel; unattributed: accepted, but the channel is unknown (the lead has no attribution)
lead_idstring | nullthe lead that was found
attribution.channelstring | nullthe channel the conversion was assigned to
attribution.basisstringthe basis: funnel, form, or none

The response field names are provisional.

#Errors

The format is application/problem+json; the codes are in the error catalog.

HTTPCodeWhen
401invalid_api_keythe key is wrong, revoked, or expired
403scope_missingthe analytics:write scope is missing
404not_foundthe lead or session was not found
403ip_not_allowedthe request comes from an address outside the integration's IP list
409idempotency_conflictthe same Idempotency-Key with a different body
422validation_failedthere is no link at all; the amount is not a string; the currency does not match the cell's currency; contact data in properties
429rate_limitedthe rate limit was exceeded
503service_unavailablethe limits store is temporarily unavailable

#Examples

#curl

curl -X POST 'https://formula-cream.pro/api/public/v1/analytics/conversions' \
  -H 'Authorization: Bearer sap_ru_live_EXAMPLE0KEYI_EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLE' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: pay_48213_1' \
  -d '{
    "conversion_id": "pay_48213_1",
    "type": "payment",
    "value": { "amount": "4990.00", "currency": "RUB" },
    "occurred_at": "2026-10-11T08:15:30Z",
    "external_id": "crm-48213"
  }'

#Python

import os, requests

response = requests.post(
    "https://wfacademy.org/api/public/v1/analytics/conversions",
    headers={
        "Authorization": f"Bearer {os.environ['API_KEY']}",
        "Idempotency-Key": "pay_48213_1",
    },
    json={
        "conversion_id": "pay_48213_1",
        "type": "payment",
        "value": {"amount": "49.00", "currency": "GBP"},
        "occurred_at": "2026-10-11T08:15:30Z",
        "external_id": "crm-48213",
    },
    timeout=5,
)
response.raise_for_status()

#How conversions reach the reports

The deals_won and revenue metrics in analytics (Reading analytics) come from the platform's CRM or from offline conversions, without double counting:

A conversion also appears in the by-channel breakdown. The strategist uses it to calculate channel payback and to prioritize topics and advertising. Data does not appear in the reports instantly: the recalculation runs after receipt.

#Refunds

A refund is passed as a conversion with a negative amount in value.amount (for example, "-4990.00") and its own conversion_id. It reduces revenue for the period that contains the refund's occurred_at.

{
  "conversion_id": "refund_48213_1",
  "type": "payment",
  "value": { "amount": "-4990.00", "currency": "RUB" },
  "occurred_at": "2026-10-20T10:00:00Z",
  "external_id": "crm-48213"
}

#Notes

#Open questions

  1. Technical. The set and names of the request and response fields are provisional: the design specifies only the idea of "a payment or a deal tied to a lead/session".
  2. For the owner. Multi-currency: whether it is acceptable to send an amount in a currency other than the cell's.
  3. Technical. The maximum age window for occurred_at and the order of recalculating payback for late conversions; partial payments and amount adjustments (other than refunds).
  4. For the owner. The analytics:write scope is shared with events; there is no separate permission for conversions (money data).