Report outcomes

Tell Subcontext what became of each lead after it left (quoted, sold, cancelled, commission paid) so your reports show what each campaign earns.

Subcontext sees a lead arrive, and everything that happens in its own conversations and forms. It only knows what happens after that, in your own CRM or a provider's systems, if you tell it. That includes the quote your adviser gave, the policy sold, the commission paid and the sale cancelled. Reporting those outcomes lets your firm's reports show what each campaign, source and advert earns.

The token you need#

Use a server-only application with read and write, and:

  • outcomes, for your firm's own systems, which can report on any lead in your organisation; or
  • crm, for a partner, which can only report on the leads it sent.

What you can send#

Your organisation's outcome types and the values each takes are set up in the dashboard, so ask for them first:

sh
curl https://subcontext.com/api/v1/crm/outcomes/model \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
json
{
  "data": {
    "types": [
      { "key": "quoted", "label": "Quoted", "repeatable": false, "product": "optional",
        "values": [ { "key": "monthly_premium", "required": false, "calculated": false },
                    { "key": "annual_premium", "required": false, "calculated": false } ] },
      { "key": "sold", "label": "Sold", "repeatable": false, "product": "optional",
        "values": [ { "key": "monthly_premium", "required": false, "calculated": false },
                    { "key": "annual_premium", "required": false, "calculated": false },
                    { "key": "commission", "required": false, "calculated": false } ] },
      { "key": "cancelled", "label": "Cancelled", "repeatable": false, "product": "optional",
        "values": [ { "key": "clawback", "required": false, "calculated": false } ] }
    ],
    "measures": [
      { "key": "monthly_premium", "label": "Monthly premium", "unit": "money" },
      { "key": "annual_premium", "label": "Annual premium", "unit": "money" },
      { "key": "commission", "label": "Commission", "unit": "money" },
      { "key": "clawback", "label": "Clawback", "unit": "money" }
    ],
    "products": [ { "key": "level-term", "name": "Level Term Life", "aliases": ["LTL", "LT01"] } ]
  }
}
  • Each entry in types is a type you can send. A type that isn't repeatable can only happen once to a lead.
  • Its values are the measures it takes. You must send the required ones, and Subcontext works out the calculated ones from the others.
  • A money unit is in whole pence (14940 is £149.40), as everywhere in the API. percent is a percentage (22.5) and count a whole number. A money value that isn't a whole number of pence is refused rather than rounded. Only GBP is supported.
  • Name a product by its key, any of its aliases (your own codes) or its name.

Report an outcome#

sh
curl -X POST https://subcontext.com/api/v1/crm/outcomes \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "sale-77812",
    "type": "sold",
    "occurred_at": "2026-10-20T14:00:00+01:00",
    "lead": { "id": "0199a3c2-8200-7522-8ea3-2738a9bacbdc" },
    "deal_ref": "DEAL-5521",
    "product": "LTL",
    "values": { "annual_premium": 14940, "commission": 26892 }
  }'
Field What it is
event_id Your own id for this outcome. Strongly recommended, because it makes sending the same one again safe.
type The outcome type's key.
occurred_at When it happened, ISO 8601. Reports count it in that month.
lead Which lead (see below).
values By measure key, in each measure's unit (money in whole pence).
deal_ref Your reference for the deal or policy. A cancellation finds its sale by it.
product Which product, by key, alias or name.
note A note for your team.

Name the lead in one of these ways, most certain first:

  • "lead": { "id": "..." }: Subcontext's id for the lead (its contact_id, sent out with every lead and returned when you send one).
  • "lead": { "external_id": "CRM-90211" }: your system's id for it.
  • "lead": { "email": "ada@example.com", "match_on_details": true } (or phone), only when you have no id. It only matches when exactly one person has those details.

Send a batch#

Send up to 500 at once as { "outcomes": [ ... ] }. Each gets its own result, in the order sent:

sh
curl -X POST https://subcontext.com/api/v1/crm/outcomes \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "outcomes": [
      { "event_id": "sale-77812", "type": "sold", "occurred_at": "2026-10-20T14:00:00+01:00",
        "lead": { "id": "0199a3c2-8200-7522-8ea3-2738a9bacbdc" }, "deal_ref": "DEAL-5521",
        "product": "LTL", "values": { "annual_premium": 14940, "commission": 26892 } },
      { "event_id": "sale-77813", "type": "sold", "occurred_at": "2026-10-20T15:30:00+01:00",
        "lead": { "external_id": "CRM-90211" }, "values": { "annual_premium": 21204 } }
    ]
  }'
json
{
  "data": {
    "counts": { "recorded": 1, "held": 1 },
    "results": [
      {
        "event_id": "sale-77812",
        "status": "recorded",
        "outcome": {
          "id": "0199a3c2-8500-7966-8ce7-6bdcedfe0f10",
          "event_id": "sale-77812",
          "type": "sold",
          "contact_id": "0199a3c2-8200-7522-8ea3-2738a9bacbdc",
          "occurred_at": "2026-10-20T14:00:00+01:00",
          "source": "api",
          "deal_ref": "DEAL-5521",
          "product": "level-term",
          "values": { "annual_premium": 14940, "commission": 26892 },
          "missing_values": [],
          "currency": "GBP",
          "reversed": false,
          "reversal_of": null
        }
      },
      {
        "event_id": "sale-77813",
        "status": "held",
        "error": "no lead matches those identifiers",
        "submission_id": "0199a3c2-8600-7a77-9df8-7cedfe0f1021"
      }
    ]
  }
}

The request as a whole answers 200 whatever happened to each outcome, so check each result's status:

status What it means What to do
recorded Matched to its lead and recorded. Nothing.
duplicate An outcome with this event_id was already recorded. Nothing. It's safe to resend.
held No single lead matched. It waits in your team's Review queue to be matched by hand. Nothing, or send it again with a better identifier.
invalid It can never be recorded as sent, for example because of an unknown type or product, a time that isn't ISO 8601, or a money value that isn't a whole number of pence. error says which. Fix it and send it again.
conflict It's valid, but clashes with an outcome the lead already has (for example a second sale of a type that happens once). Check what's recorded and reverse the wrong one if needed.

Only a batch that's empty or over 500 is refused as a whole (422).

Cancellations and clawbacks#

When a sale is cancelled later, send a cancelled outcome dated when it was cancelled, with the sale's deal_ref (it can be left out if the lead has only one sale), a reason, and any commission clawed back:

json
{
  "event_id": "cancel-77812",
  "type": "cancelled",
  "occurred_at": "2026-12-02T09:00:00Z",
  "lead": { "id": "0199a3c2-8200-7522-8ea3-2738a9bacbdc" },
  "deal_ref": "DEAL-5521",
  "reason": "Cancelled within the cooling-off period",
  "values": { "clawback": 26892 }
}

The sale still counts in its own month. The cancellation and clawback count in December.

Correct a mistake#

To undo an outcome sent in error, reverse it by the event_id you sent it with (or by Subcontext's id, with Reverse an outcome):

sh
curl -X POST https://subcontext.com/api/v1/crm/outcomes/reverse \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "event_id": "sale-77812", "note": "Recorded against the wrong customer." }'
json
{
  "data": {
    "id": "0199a3c2-8501-7a77-9df8-7cedfe0f1021",
    "event_id": null,
    "type": "sold",
    "contact_id": "0199a3c2-8200-7522-8ea3-2738a9bacbdc",
    "occurred_at": "2026-10-20T14:00:00+01:00",
    "source": "api",
    "deal_ref": "DEAL-5521",
    "product": "level-term",
    "values": { "annual_premium": 14940, "commission": 26892 },
    "missing_values": [],
    "currency": "GBP",
    "reversed": false,
    "reversal_of": "0199a3c2-8500-7966-8ce7-6bdcedfe0f10"
  }
}

The answer is the reversal, an outcome of its own that points at the one it reverses in reversal_of. The record keeps both, and reports stop counting it. To correct an outcome, reverse it and send the right one with a new event_id. Reversing one that's already reversed answers 409 already_reversed.

Use a reversal only for mistakes. For a sale that was cancelled, send a cancellation, so the sale still counts in its month.

Read back what's recorded#

List outcomes gives the outcomes recorded, oldest first, a page at a time (pass next_cursor back as cursor, as every list in the API pages). Filter by contact_id, deal_ref or type. A type that isn't one of yours answers 422 invalid_parameter. The list includes outcomes your firm recorded in the dashboard or from spreadsheets, and the ones Subcontext records itself (such as quoted and sold for policies sold through it).

Stuck, or something missing?

Get in touch and a person on the team will help.

Contact us