Achievements can be an exciting feature for any consumer-facing web or mobile application yet can be surprisingly complex to manage at scale. Writing logic to manage unlocks for different types of achievements across millions of users can quickly turn a clean implementation into spaghetti code. In this blog I’ll walk through best practices for adding achievements to a NextJS flashcard study platform using Trophy.
Trophy is a gamification platform with scalable APIs for building features like achievements and streaks taking care of all unlock logic under the hood. Here’s an example of the flashcard study system built using Trophy’s APIs. Check out the live demo here.
Adding Achievements to a NextJS Web App In Under 10 Minutes
In this video I walk through the code behind the application and explain how Trophy automatically manages all achievements behind the scenes. Plus, I'll show you how to use Trophy’s NodeJS SDK to fetch and display user achievements in the app.
Key Points
No-code Trophy Setup
In the video I walked through setting up a flashcards-viewed metric with 4 achievements of increasing difficulty in the Trophy dashboard.

The achievement names, badges and unlock values are all controlled from the dashboard, and so can be experimented with without requiring back-and-forth code changes. Similarly, adding new achievements can be done from the dashboard and will automatically be available for all users to work towards without needing any code changes.
Integrating Metric Tracking
In the video I set up a Trophy metric to track when users view a flashcard. This sets up a running event stream and forms the basis of the achievements system.
Tracking metrics based on any user interaction in Trophy is just a simple API call:
/**
* Track a flashcard viewed event in Trophy
* @returns The event response from Trophy
*/
export async function viewFlashcard(userId: string): Promise<EventResponse | null> {
try {
return await trophy.metrics.event(FLASHCARDS_VIEWED_METRIC_KEY, {
user: {
id: userId
},
value: 1
});
} catch (error) {
console.error(error);
return null;
}
}
Fetching Achievements
Fetching a users unlocked achievements is a simple Trophy API call. This call returns each achievements name, as well as a URL to the achievements badge hosted on Trophy's CDN that is ready to be used in src tags.
/**
* Get the achievements for a user
* @returns The achievements for the user
*/
export async function getAchievements(userId: string): Promise<MultiStageAchievementResponse[] | null> {
try {
return await trophy.users.allachievements(userId);
} catch (error) {
console.error(error);
return null;
}
}
Making It Production-Ready: Idempotency, Duplicate Awards, And Errors
The snippet above works in a demo. Production is messier: the same event can fire more than once, and counting it twice unlocks the wrong achievement or hands out points a user never earned. Here is how to make the integration safe.
Why Duplicate Awards Happen
Events double up for ordinary reasons:
- A user double-clicks the "View Flashcard" button.
- A request times out, so your code retries it.
- Your event pipeline delivers at least once, so it sometimes delivers twice.
- A buggy trigger fires on every re-render instead of once per action.
Each of these sends the same metric increment more than once. Trophy counts two events and unlocks an achievement the user hit only halfway. Points inflate, and your leaderboard stops reflecting real behavior.
How Trophy Prevents Double-Awarding
Pass an idempotency key on every event. In the SDK it is the idempotencyKey field, which maps to the Idempotency-Key HTTP header. Trophy processes an event with a given key one time, so if the same key arrives again from a retry or double-click, it ignores the duplicate.
Use a stable business ID as the key, not a random value. The lesson ID or study-session ID works well, because a retry of the same action reuses the same ID and Trophy recognizes it. Here is the flashcard snippet from the Key Points section, updated to pass the key:
async function viewFlashcard(userId: string, sessionId: string, cardId: string) {
await trophy.metrics.event(FLASHCARDS_VIEWED_METRIC_KEY, {
user: { id: userId },
value: 1,
// Reuse a stable business ID so retries collapse to one event.
idempotencyKey: `flashcard-view-${sessionId}-${cardId}`,
});
}
Trophy's platform is idempotent and retry-safe, with exactly-once processing built in. You get that without building and maintaining a deduplication pipeline of your own.
Counting Each Action Once
Idempotency stops the same call from being counted twice. Event uniqueness handles a different case: the same action happening again later.
Trophy can enforce that a user increases a metric only once per unique action. If a learner completes the same lesson today and again next week, it counts once. Turn this on for "reward this once" logic, and pair it with idempotency keys for full coverage.
Handling Errors And Status Codes
Wrap the Trophy call in error handling and keep it off the critical path. A learner should still see their next flashcard even if the event call fails. Log the failure and retry it in the background, where the idempotency key makes the retry safe.
Handle these cases:
- Authentication failures. A missing or invalid API key. Check your environment configuration and confirm the key has the right permissions.
- Rate limiting. Too many requests in a short window. Back off and retry with the same idempotency key.
- General request failures. Network timeouts, transient server errors, or malformed payloads. Log the response and retry safely.
For the authoritative list of status codes and error messages, see the Trophy API reference. Trophy also gives you detailed event logs, so you can trace exactly which events were accepted, deduplicated, or rejected.
Try Trophy
Trophy provides scalable, purpose APIs for building achievement systems in any web or mobile application. Trophy also has support for building other gamification features like streaks and gamified lifecycle email campaigns with very little custom code.
Create an account and try Trophy for free up to 1,000 monthly active users.

Get the latest on gamification
Product updates, best practices, and insights on retention and engagement — delivered straight to your inbox.
The gamification layer for consumer apps
Drop-in gamification features you can ship this sprint. Increase retention and user engagement without sacrificing your roadmap.
Book a call