From feedback to changelog: closing the loop
A workflow for turning raw user feedback into shipped changes and a public changelog — using Loop's tags, statuses, and webhooks to keep users in the loop automatically.
There is a quiet failure mode in most feedback systems: the loop never closes. A user reports something, it gets read, maybe it even gets fixed — and the user never hears back. From their side, the feedback vanished. The next time you ask for input, they remember that silence.
Closing the loop means connecting the moment someone tells you something to the moment they see it addressed. This post is a concrete workflow for doing that with Loop, ending in a changelog that writes much of itself.
The four stops on the way to "shipped"
Every piece of feedback that turns into a change passes through the same stops. Naming them makes the workflow legible:
- Captured — the item exists, with a
user,message, andsentiment. - Triaged — it has a
tagand an owner, and itsstatusisopen. - Resolved — the work is done;
statusbecomesresolved. - Announced — the change shows up in a changelog, and the original reporter is told.
Most teams do the first three by hand and skip the fourth. The trick is to let the data carry enough structure that the last two happen on their own.
Capture with intent
Feedback is most useful when it arrives already shaped. Whether it comes from the widget or the SDK, attaching a source and a meaningful sentiment at capture time is what makes everything downstream automatable.
import { Loop } from '@loop/sdk';
const loop = new Loop(process.env.LOOP_API_KEY);
await loop.feedback.create({
user: { id: 'u_8f2c' },
message: 'A dark mode for the dashboard would help on late shifts.',
sentiment: 'neutral',
source: 'in-app-widget',
tag: 'feature-request',
});
A neutral feature request and a negative bug report deserve different treatment. Because the sentiment is set at the source, your triage rules can split them without anyone re-reading the text.
Triage as grouping, not sorting
Triage is usually framed as sorting one item at a time. It is more powerful as grouping. Twelve people asking for dark mode is not twelve tickets; it is one signal with a count.
Tags are how you collapse that noise into a theme. Once a cluster of feedback shares a tag like feature-request:dark-mode, you have something a roadmap can actually consume: a named demand with a list of people attached to it.
The reporters attached to a theme are the people you will notify when it ships. That list is the entire reason to keep them connected to the work.
When the work is picked up, flip the items to open and assign an owner. Nothing magical here — just enough state that a webhook can react to changes later.
Resolve, and let the webhook do the announcing
This is where the loop closes. When you mark an item resolved, Loop emits a webhook. That event is your trigger to do two things automatically: append to your changelog, and notify the person who asked.
from loop import Loop
import os
loop = Loop(api_key=os.environ['LOOP_API_KEY'])
def on_resolved(event):
item = event['data']
# 1. Append a changelog entry keyed by the item's tag
changelog.append(tag=item['tag'], summary=item['message'])
# 2. Tell the original reporter their request shipped
notify(user_id=item['user']['id'], message='The thing you asked for is live.')
The reporter gets a message that references their own words. That specificity is what makes people keep giving you feedback: they have evidence it goes somewhere.
A few things make this reliable rather than fragile:
- Webhooks are signed, so your handler can trust the event before acting on it.
- Delivery is exactly-once, so you will not post the same changelog entry twice or double-notify a user.
- Retries are automatic, so a brief outage in your changelog service does not drop an announcement.
The changelog as a byproduct
The best changelogs are not written in a separate ritual at the end of a sprint. They accumulate as a side effect of resolving real feedback. Each entry already has the raw material it needs: the theme (from the tag), the motivation (from the original messages), and the audience (from the reporters).
What you publish can stay terse — a date, a short description, a link. The depth lives in the trail of feedback behind it. When someone reads "Added dark mode for the dashboard," you know precisely who asked, what they said, and when, because the changelog grew out of those items rather than being reconstructed after the fact.
Start with one rule
You do not need to automate the whole pipeline at once. Start with a single webhook handler that does one thing when an item is resolved — post a line to a channel, or message the reporter. Watch how it changes the feeling of giving feedback in your product. The loop, once closed even partway, tends to stay closed.