message.comDevelopers

Track conversion from chat to paid

Attribute paid revenue to the chats that influenced it. The pattern is the same whether you charge with Stripe, Shopify Checkout, or your own billing: stitch the message.com conversation ID into the checkout metadata, then on the success webhook, post a goal back to the conversation.

The goals API is currently planned. The pattern below works today by writing the same data into visitors.upsert custom attributes (e.g., lastConversionConvId, lastConversionAmount) and reporting from there. Switch to the dedicated goals endpoint when it ships.

1. Capture the conversation ID

Listen for the conversation:started event and persist the ID in sessionStorage. Survives the trip to checkout.

HTML
<script>
  window.MessageOnReady = function () {
    window.Message.on("conversation:started", (conv) => {
      // Persist for the Stripe / Shopify webhook to find later
      sessionStorage.setItem("messageConversationId", conv.id);
    });
  };
</script>

2. Forward the ID to checkout

When the visitor clicks the Buy button, read sessionStorage.messageConversationId and post it to your checkout endpoint. Use it as metadata on the payment session.

server.ts · create checkout
// Server-side: when creating a Stripe Checkout Session
const session = await stripe.checkout.sessions.create({
  mode: "subscription",
  line_items: [...],
  metadata: {
    messageConversationId: req.body.messageConversationId, // forwarded from browser
  },
});

For Shopify Checkout, use the cart.attributes field. For your own billing, store it alongside the order.

3. Record the goal on webhook

On checkout.session.completed (Stripe) or orders/paid (Shopify), read the conversation ID from metadata and post a goal to the conversation.

server.ts · stripe webhook
// Server-side: handle checkout.session.completed
if (evt.type === "checkout.session.completed") {
  const session = evt.data.object;
  const convId = session.metadata?.messageConversationId;
  if (convId) {
    await fetch(`https://app.message.com/api/v1/conversations/${convId}/goals`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.MESSAGE_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        goalType: "purchase",
        amount: session.amount_total / 100,
        currency: session.currency,
        externalId: session.id,
      }),
    });
  }
}

4. Report

Two reporting paths:

  • Goals report (when goals API ships). GET /api/v1/reports/goals?range=30d&type=purchase returns chat-attributed revenue, conversion rate, and agent attribution.
  • Visitor-attribute report today. Filter visitors where lastConversionConvId IS NOT NULL over the date range. Sum lastConversionAmount.

Attribution model

Decide which chat gets credit if a visitor has more than one:

  • Last touch. The most recent chat before the purchase. Easiest. Default.
  • First touch. The first chat ever, even months earlier. Useful for top-of-funnel teams.
  • Linear. Split credit across every chat. Hard to interpret in practice.

Most teams pick last touch and stop overthinking it.

Common pitfalls

  • Visitor opens a fresh window for checkout. sessionStorage does not survive a new tab. Use localStorage if your checkout opens in a new window, but expire the ID after 24 hours.
  • Anonymous to identified. If the visitor signs in after the chat starts, the conversation re-keys to the identified visitor. The conversation ID is stable.
  • Refunds. Subscribe to refund.created and record a negative goal (or delete the goal) so the report stays honest.
  • Multi-agent chats. If two agents touched the conversation, attribution can split or pick the closer. Decide once and document.

Next steps