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.
<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-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-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=purchasereturns chat-attributed revenue, conversion rate, and agent attribution. - Visitor-attribute report today. Filter visitors where
lastConversionConvId IS NOT NULLover the date range. SumlastConversionAmount.
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.
sessionStoragedoes not survive a new tab. UselocalStorageif 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.createdand 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.