Documentation

Outcome-label connectors

Automate credit_burn, abuse_confirmed, and chargeback labels so outcomes arrive without a human calling the API.

Connectors automate those labels so outcomes arrive without a human calling the API.

Fast labels (credit_burn, multi_account_cluster, abuse_confirmed) mature in hours, not months: they are the pilot proof metric. A production-ready credit-burn connector pattern ships in the open-source examples/label-connector package.

Recipe A: usage metering → credit_burn

Meter credit/infra consumption per account, then drain that queue into feedback with the burned USD value:

// Consumer for your usage/credit-metering queue (per-account aggregates).
meteringQueue.on("credit_usage_bucket", async (bucket) => {
  // bucket: { accountId, creditsBurnedUsd, observedAt }
  const account = await db.accounts.findById(bucket.accountId);
  if (!account.chitmarkEventId) {
    log.warn("missing chitmarkEventId", { accountId: bucket.accountId });
    return; // signup never stored it: fix the verify-time persistence
  }
await chitmark.feedback({
  eventId: account.chitmarkEventId,
  outcome: "credit_burn",
  value: 12.4, // economic magnitude: burn $, chargeback $, or converted first-year ARR
  unit: "usd", // scale for value: usd | credits | count (default: usd)
  source: "usage_metering",
});
});

Recipe B: T&S queue → abuse_confirmed

When an abuse decision lands in your trust-and-safety queue, emit a label keyed the same way:

trustSafetyQueue.on("abuse_action_taken", async (action) => {
  // action: { accountId, reason, decidedAt }
  const account = await db.accounts.findById(action.accountId);
  if (!account.chitmarkEventId) return; // never guess: skip and fix signup
  await chitmark.feedback({
    eventId: account.chitmarkEventId,
    outcome: "abuse_confirmed",
    source: "trust_queue",
    observedAt: action.decidedAt,
  });
});

Recipe B2: T&S clear → false_positive

false_positive means a previously attributed abuse signal on this eventId was reviewed and determined not to represent abuse (T&S clear / overturn). It is not "Chitmark challenged or denied a legitimate user" (context.decisionCosts.falseChallenge / falseDeny on verify) and not a generic "external classifier was wrong" unless that correction is about abuse attribution joined on this eventId. Misusing it contaminates labeled feedback.

trustSafetyQueue.on("abuse_cleared", async (action) => {
  // action: { accountId, decidedAt }: prior abuse label overturned
  const account = await db.accounts.findById(action.accountId);
  if (!account.chitmarkEventId) return;
  await chitmark.feedback({
    eventId: account.chitmarkEventId,
    outcome: "false_positive",
    source: "trust_queue",
    observedAt: action.decidedAt,
  });
});

Recipe C: billing → chargeback

Stripe webhooks resolve the event id via your stored mapping, never a guess:

stripeWebhook.on("charge.dispute.created", async (evt) => {
  // Resolve from the account row, not from the dispute payload.
  const eventId = await db.accounts.chitmarkEventIdFor(evt.data.object.customer);
  if (eventId)
    await chitmark.feedback({
      eventId,
      outcome: "chargeback",
      value: evt.data.object.amount / 100,
      unit: "usd",
    });
});

Delivery semantics

Retry transient failures (5xx / network) with your queue's dead-letter policy. Exact duplicate deliveries derive the same warehouse feedbackId when eventId, outcome, value, unit, and observedAt match (prefer a stable business observedAt plus an Idempotency-Key on retries). Unknown or cross-tenant event ids return 404 unknown_event_id: the edge never acknowledges a label generically.