# Achievement System Source: https://docs.nudj.cx/admin-guide/achievements Guide members through meaningful milestones without turning the Control Room into a maze Achievements are your story engine. They mark progress, nudge habits, and unlock special moments without needing constant hand-holding. As Plato said, "The measure of a person is what they do with power"—and achievements measure what users do when empowered. This overview points you to the four guides that matter most. ## What Achievements Do Well Recognise everything from first steps to marathon streaks. Encourage the actions you value—learning, sharing, championing others. Pair XP, points, or bespoke treats so effort feels worthwhile. After each unlock, guide members to the next challenge instead of leaving them idle like penguins without a waddle. ## Your Achievement Toolkit Turn the system on, set expectations, and organise the basics. Plan behaviours, configure criteria, and package rewards your flock will chase. Read the signals, tweak difficulty, and automate adjustments. Proven tips, common pitfalls, and refresh ideas when you need a creative spark. ## Launch Checklist Capture the goal in one sentence before you open the builder. Daily, weekly, monthly, seasonal, or lifetime—match it to the habit. Configure criteria, rewards, and copy; test with a staging profile. Tell your community what’s new and why it matters—no surprises for the herd. Use analytics to keep difficulty and rewards in the Goldilocks zone. Ready? Dive into **Getting Started** to switch everything on, then glide over to **Designing Achievements** when inspiration strikes. The rest of the Control Room is just a sidebar away. # Achievements as Actions Source: https://docs.nudj.cx/admin-guide/achievements/achievements-as-actions Gate challenges behind earned achievements — the Complete-Achievement action pattern A **Complete-Achievement action** (#729) is a special challenge action that requires the member to have already earned a specific achievement. It gates progression. ## How it works Add the Complete-Achievement action to a challenge: 1. Pick an existing achievement. 2. Members who haven't unlocked that achievement see a **locked** state on the challenge. 3. Once they unlock the achievement (through the achievement's normal criteria), the challenge becomes playable. *** ## Why this pattern Each tutorial challenge requires the previous one. Members finish in order. Bronze -> Silver -> Gold challenges, each gated behind the previous tier. Advanced challenges require baseline knowledge (unlocked via a "completed basics" achievement). Gate special content behind an achievement earned through repeated engagement. *** ## Creating the pattern E.g. "Bronze Completer" — criteria: `challenge_completed count >= 5`. * New -> Challenge. Fill out basics. On the Content step, add a **Complete Achievement** action. Pick your source achievement. Add the actions that make up the actual challenge content. Members who've earned Bronze Completer can play. Others see the lock. *** ## Layering with keys Combine with [key-gated challenges](/admin-guide/challenges/unlock-keys) for multi-dimensional gating: > Unlock: requires 3 keys AND Bronze Completer achievement. Members must satisfy both to play. *** ## Note on stability Earlier versions of this feature had a page-crash on action-achievement rendering (#955) — fixed. If you're on a very old build and see crashes, force-refresh. *** ## Best practices Don't gate **every** challenge. If members hit too many locked doors, they disengage. Rule of thumb: 80% open, 20% gated for progression. **Make the source achievement visible**. If a member sees a locked challenge but doesn't know how to unlock it, they give up. Link the achievement in the challenge description. # Designing Achievements Source: https://docs.nudj.cx/admin-guide/achievements/designing-achievements Criteria builder, reward distribution, tiered achievements, multi-language support A good achievement turns noisy behaviour into a clear milestone members can see and aspire to. The builder has three parts: criteria (what triggers it), reward (what members get), and scope (who can earn it). ## Criteria Pick the event type + threshold. Events fall into three groups: ### Built-in events | Event | Fires when | | - | - | | `challenge_completed` | Member completes a challenge | | `reward_earned` | Member earns any reward | | `check_in_completed` | Daily check-in action | | `streak_milestone` | Streak hits a configured length | | `level_reached` | Member hits an XP level | | `member_joined` | Member joins the community | ### Custom events Use the **free-text custom event identifier** (#2144) to match events you fire via the Integration API. Example: `purchase_completed`, `livestream_attended`, `survey_submitted`. ### Thresholds Stack conditions: * `count >= N` * `within Xd` (rolling window) * Filter by tag, category, or other event attributes. *** ## Reward distribution Attach rewards that distribute when the achievement unlocks: * Points, XP (instant addition to balance) * Badge (shown on profile) * Asset reward (adds to wallet) * Entry reward (adds entries to a pending giveaway) Multiple rewards per achievement are supported — a single unlock can grant a badge plus 500 points plus 3 entries. *** ## Achievements as actions The **Complete-Achievement action** (#729) inverts the relationship: a challenge requires members to have already unlocked an achievement before it can be started. This gates progression — useful for multi-stage programs like tutorials or certification paths. See [Achievements as Actions](/admin-guide/achievements/achievements-as-actions). *** ## Tiered achievements Design a series of escalating milestones: ```text theme={null} Bronze -> Silver -> Gold -> Platinum 5 done 20 done 50 done 100 done ``` Implementation: 1. Create four achievements, each with identical criteria shape but different thresholds. 2. Link Silver -> Bronze via a Complete-Achievement action in the next challenge (or on the achievement's own gating rules). 3. Members unlock them sequentially as they accumulate. *** ## Multi-language support Achievements inherit your community's language config. Edit translations per locale; the achievement's title, description, and badge alt-text all translate. The language-switch bug in the achievement module was fixed in #845 — you can safely toggle between locales in the editor without losing your draft. See [Language Management](/admin-guide/settings/language-management). *** ## Scope decisions Default. Only counts events inside that community. Good for community-specific milestones ("Top 5 contributors in the Paris region"). Counts events across every community in the org. Good for brand-wide milestones ("100 total check-ins across all our stores"). Scope is set at creation and **immutable** afterwards. Re-create the achievement if you need to change scope. *** ## Best practices Start with 3-5 achievements for a new community. More than that and members can't hold the full picture in their head. Pair each achievement with a **visible reward** (badge + points + something spendable). Achievements without visible rewards feel hollow. Achievements with thresholds that take months to hit (e.g. `count >= 1000`) look like broken progress bars. Use tiered achievements instead so every 10-20% of progress unlocks something. # Getting Started with Achievements Source: https://docs.nudj.cx/admin-guide/achievements/getting-started Design, scope, and launch achievements with community-scope defaults and self-healing Achievements are milestones members unlock by accumulating specific actions over time. They're separate from challenges (which are discrete, repeatable units) — achievements are passive accumulators. ## Creating an achievement Top nav -> **+ New -> Achievement**. Title, description, icon/badge image. Form ships with a loader to prevent double-submit (#939) and ergonomic fixes for common error flows (#2239). New achievements default to the **current community's scope** (#2151). Change to **org-level** if the achievement should unlock across every community in the org. See [Cross-community scope](/admin-guide/rewards/advanced-features#cross-community-rewards). Pick the event that unlocks this achievement: * **Built-in events** — challenge completed, reward earned, check-in, streak milestone, etc. * **Free-text custom event identifier** (#2144) — for events you fire via the API. Add thresholds: `count >= 10`, `within 30d`, etc. Attach points, XP, a badge, or any other reward. Achievements can carry their own reward distribution — see [Designing Achievements](/admin-guide/achievements/designing-achievements). Achievements appear in the list immediately (#1897 fixed the "saved but doesn't appear" bug). Duplicate names show a toast warning (#734). *** ## Self-healing for dangling achievements Nudj's achievement processor (#2111) self-heals when it finds an achievement with a deleted source (e.g. the challenge that fires the event was archived). Rather than crashing, the processor logs and skips — you'll see these in BetterStack logs if anything's wrong. When you archive a challenge, audit its linked achievements. Re-point the event criteria or archive the achievement too. *** ## Listing & managing The achievement list uses sticky columns (#782) for the key fields (title, status, scope, reward). Filter by status, scope, or tag. Sort is persisted. *** ## Common patterns Criteria: `member_joined` event, count 1. Reward: welcome badge + 100 points. Criteria: `check_in_streak >= 7`. Reward: fire badge + XP boost. Criteria: `challenge_completed count >= 50`. Reward: Gold badge + spotlight reward. Use the free-text custom event identifier. Example: event `purchase_completed` fired from your Shopify webhook; criteria `count >= 3 within 90d`. Reward: VIP badge. *** ## Next steps Criteria builder, event types, multi-language, form patterns. Gate challenges behind earned achievements. Manual-approval achievement flow. Strategy for tiered progression systems. # Measure & Optimise Source: https://docs.nudj.cx/admin-guide/achievements/measure-and-optimise Read the signals, tune difficulty, and keep achievements humming Launching an achievement is only half the story. This guide shows you how to watch the numbers, spot trends, and make smart adjustments before your community loses interest—or races through everything like cheetahs on espresso. ## The Dashboard in Plain English Shows how many eligible members actually unlock the achievement. Sweet spot: most engaged members finish it, but it still feels earned. Tracks how long it takes from first qualifying action to unlock. A slow crawl signals confusion or an overly ambitious target. Compares behaviour before and after completion. Proof that your badge isn’t just shiny—it’s effective. Keeps an eye on cost vs. value. If XP or prizes outpace engagement, dial things back. Visit the analytics tab weekly for new achievements, monthly for evergreen ones. Early adjustments keep the experience fair and fun. ## Quick Troubleshooting Table | Symptom | What it Means | Next Move | | - | - | - | | Everyone completes it within hours | Too easy | Raise the threshold or shorten the reset period | | Almost nobody starts | Too hard or unclear | Simplify the criteria, add guidance, or lower counts | | Members abandon halfway | Missing direction | Add a follow-up message or pair with a challenge | | Reward costs balloon | Incentives misaligned | Reduce XP/points or offer lighter bonuses | ## Light-Touch Automations Create a rule that nudges thresholds up if completion stays above 70%, and down if it falls below 10%. Saves you from constant manual tweaking. Schedule achievements to publish and retire automatically with each campaign. Update art and copy once, let the calendar do the rest. Trigger a Control Room automation that sends a congratulatory message—or a next-step challenge invite—the moment someone unlocks a badge. Send Slack or email alerts if processing stalls or inventory for related rewards runs low. Better to hear about it from an internal monitor than from a frustrated llama. ## When You Need Raw Data Download CSVs to slice and dice in spreadsheets. Perfect for quarterly reviews. Subscribe to `achievement.unlocked` or `achievement.progress` events and pipe them into your CRM, analytics tool, or celebration channel. Pull analytics or update thresholds programmatically when you need fine-grained control. The Control Room API docs have copy-paste examples. ## Keep the Loop Going Retire outdated achievements, refresh art, and note any lessons learned. Treat it like grooming a show dog: little and often. Combine data with human feedback. A quick poll or forum thread can surface insights the charts miss. You now have the tools to keep achievements performing long after launch day. Hop back to **Designing Achievements** when you’re ready to create the next chapter, or scan the **Control Room Playbook** for fresh ideas. # Achievement Playbook Source: https://docs.nudj.cx/admin-guide/achievements/playbook Design achievements that feel fair, purposeful, and downright fun Great achievements feel like a guided adventure: clear path, satisfying payoff, and a little story members can retell. Use these principles to keep your ecosystem thriving without overwhelming anyone (yourself included). ## Start with the Why Decide which behaviour you want to spark or reinforce Match the effort to a reward that genuinely feels worth it Write a description that explains the win in plain language If an achievement doesn’t serve a clear purpose, park it for later. No need to release every shiny idea at once like an overexcited magpie. ## Build a Gentle Ladder Quick wins for new members (profile complete, first challenge). Opens the door with a smile. Daily and weekly goals that reinforce behaviour. Think otters returning to the same favourite rock. Aspirational milestones or seasonal spotlights. Keep these rare so they stay special. Aim for a healthy mix: \~30% quick wins, \~40% moderate challenges, \~20% advanced goals, and a small handful of elite feats. ## Make Progress Obvious * Use descriptive copy (“Complete three live sessions”) instead of developer shorthand. * Keep criteria aligned with the tracking period. Daily achievements should feel doable before the sun sets. * Preview the achievement to ensure the progress bar moves as expected—nothing discourages a herd faster than a frozen gauge. ## Celebrate Without Overwhelming Profiles, feeds, and celebratory toasts should cheer the member on without spamming the pack. Send them selectively. Save the loud horn for moments that truly matter. Balance XP, points, or physical rewards with your broader economy. Golden eggs are great, but not for every nest. ## Review and Refresh If completion sits above 70% for weeks, raise the bar. If it’s below 10%, ease up or add more guidance. Retire outdated achievements and introduce fresh themes to keep curiosity alive. Collect feedback through surveys, forums, or live sessions. Qualitative insights round out the numbers. ## Team Play * Pair achievements with challenges or campaigns so members always know the next step. * Share big unlocks in Slack or your CRM to keep internal teams in the loop—everyone loves cheering on the flock. * Document changes in a simple log so future you remembers why thresholds moved. Use these suggestions as a living playbook. Achievements thrive when they evolve with your community, not when they stay frozen in amber like ancient mosquitos. # Reviewing Submissions Source: https://docs.nudj.cx/admin-guide/achievements/reviewing-submissions The review-achievement flow for manual-approval actions: queue, approve, reject Some actions require human approval — e.g. image uploads for a UGC campaign, free-text answers for quality control, or achievement claims with verifiable evidence. Nudj has a dedicated **review flow** (#1195) for admins to work through these submissions. ## Where to find it Admin panel -> Engagement -> **Review Queue** (or via individual action analytics). *** ## Queue view Pending submissions show up as rows with: * Member (username + profile link) * Action (which challenge, which action) * Submission preview (thumbnail for images, excerpt for text) * Submitted at timestamp *** ## Approve / reject Click a row to open the full submission: Marks the submission valid. Member's action counts as complete; any attached reward distributes. Member's action fails. They can retry (if the challenge allows) or the challenge fails for them. Send a short message to the member with the decision — useful for explaining rejections. *** ## Bulk review Select multiple rows and bulk-approve / bulk-reject. Useful for clearing out a backlog after a big UGC campaign. Bulk reject is permanent per-member. Make sure you're actually rejecting, not just batch-clearing stale data. Use archive for the latter. *** ## Configuring review-required actions Moderation on an action is a config flag in the action editor. See [Rich Media Actions](/admin-guide/challenges/rich-media-actions) for image moderation, and question-actions for free-text moderation settings. *** ## Best practices **Set an SLA** internally — e.g. "we'll review submissions within 24 hours". Tell members in the challenge description. They expect feedback. Use the **message** action for rejections — a one-line explanation beats a silent no. Members who understand "why" are more likely to try again. For high-volume UGC, use **object detection** (automated) instead of manual review — faster, cheaper, and scales. # Control Room Quickstart Source: https://docs.nudj.cx/admin-guide/admin-quickstart Get your first community live in about 15 minutes **Ready to go live?** This guide gets you from signup to working community in about 15 minutes. ## Before you start You have an email address. You know roughly what behaviour you want members to repeat. You have one reward in mind. *** ## The path Go to **[admin.nudj.cx](https://admin.nudj.cx)** and sign in. On first sign-up you'll get a **single-step organisation creation flow**. After you save, you're redirected straight into **Create Community**. In **Create Community** give it a name, slug, a short description, and a banner image. Save as **Draft**. Pick a name members will see in the app. You can rename later, but slugs stick. Go to **Organisation Settings -> Customisation -> Branding & Appearance**. Upload your logo, pick accent colours, choose a font. The **live preview iframe** on the right shows your changes. See [Branding & Appearance](/admin-guide/settings/branding-theming). Click the **+ New** button and pick **Challenge**. The **creation-method modal** gives you three options: start from scratch, duplicate an existing item, or ask the AI agent to draft it. Add a title, one quiz question, and a point reward. Save as **Draft** while you test. From **+ New -> Reward**, pick the type (Asset / Entry / Points / XP), fill out the studio layout, set an allocation supply, and save. See [Creating Rewards](/admin-guide/rewards/creating-regular-rewards). Reopen your challenge, go to the **Distribution** step, and attach the reward. Hit **Save Draft**. Every challenge has a **preview** button that opens the experience as a member would see it — desktop and mobile side-by-side. See [Preview Mode](/admin-guide/challenges/preview-mode). In the challenge, change **Status** to **Live** and save. Future-dated challenges automatically appear as **Scheduled**. Back in **Community Settings -> Status**, pick **Live** and confirm. Nudj pops a confirmation dialog so you can't flip the wrong one by accident. Send people your community URL, or enable the embed/FAB. See [Users Overview](/admin-guide/user-management/overview). *** ## You're live Members can now join, complete your challenge, and claim a reward. ## What's next Tour the drill-down sidebar and the top-nav community switcher. Templated commands, voice mode, and what the agent can build for you. Full challenge creation walkthrough. Roles: Viewer, Moderator, Manager, Admin. Most successful communities launch with **3-5 challenges** before opening to members. # Nudj Agent Source: https://docs.nudj.cx/admin-guide/ai-agent The AI co-pilot at the centre of the admin: a full chat workspace with live preview, a task queue, tool cards, image generation, and voice The Nudj Agent is a conversational AI co-pilot that plans, creates, and edits your loyalty program. It drafts challenges, rewards, achievements, and posts, tunes branding, generates images, and previews the result live — all inside a full-height chat workspace. This page covers what it can do and how to drive it. ## Two agent modes | Mode | Who sees it | What it can do | | - | - | - | | **Planner** | Everyone (incl. Viewer and Moderator roles) | Answer questions, draft ideas, read your platform data (list/get) | | **Builder** | Manager, Admin, and SuperAdmin roles | Everything Planner does, plus **create, update, and delete** challenges, rewards, achievements, posts, and communities | The agent derives your mode from your **RBAC role** — there is no manual switch. Viewers and Moderators stay in Planner mode; Managers and above get full Builder access. See [Roles & Permissions](/admin-guide/settings/rbac-and-roles) for role details. *** ## Opening the agent There are two surfaces, both backed by the same agent: * **The chat workspace** — the dedicated full-height chat at `/chat`, with the workspace panel beside it. * **Ask Nudj** — the persistent chat bar at the bottom of the admin. Contextual pages can open it pre-loaded with what you're looking at. The agent picks up your active **organisation** and **community** from context automatically — you never paste IDs. *** ## The workspace panel Next to the chat sits a panel with four tabs. Open it from the top-bar toggle or when the agent surfaces something. A **live widget preview** the agent drives as it works — see changes land in the real member experience. Everything created or changed **this session**, grouped by type, so you can review the run at a glance. Your **Brand Memory** — the company background, voice, and rules the agent writes from. The **Setup Guide** — press Start on any onboarding task and the agent walks you through it. See [Chat Setup Guide](/admin-guide/chat-setup-guide). You can **expand** the panel to full width when you want to focus on the preview, and drag the divider to resize the split. *** ## Live widget preview The Preview tab renders the actual Nudj widget — pixel-identical to what members see on your storefront. The agent navigates it for you: ask it to build a challenge and the preview jumps to show it. The preview is also **interactive**. From any section in the preview you can: * **Edit** the section in a focused, tabbed editor (Content / Layout / Style). * **Move** a section up or down. * **Hide** a section. * **Ask the agent** about a section — sends a section-scoped message into the composer. Because the agent and these controls write to the same community theme, you can mix approaches: ask the agent to restyle a section in words, then nudge one exact colour by hand. See [Widget Section Theming](/admin-guide/settings/widget-section-theming). Attach what you're previewing to your message — the composer can carry the current preview context to the agent so it edits exactly what you're looking at. *** ## Tool cards & entity deep links When the agent reads or writes your data, it renders a **tool card** in the conversation — a compact summary of what it did (a challenge it created, rewards it listed, stats it pulled). Cards for entities carry deep links: jump straight to **Preview** the item in the panel or **Edit** it in the admin. *** ## Prompt queue & steering You don't have to wait for the agent to finish before lining up the next step. * **Queue** follow-up instructions while the agent is working — they run in order, and the queue pauses on any step that needs your approval. * **Steer** mid-run — send a priority message that interrupts the current step and jumps the queue when you need to correct course immediately. The queue shows each item's status (queued, running, waiting for approval, sent, failed, or cancelled), so a long build is easy to follow. *** ## Image generation Ask the agent for artwork — a challenge banner, a reward image, brand imagery — and it generates it. Image generation runs **asynchronously**: the agent keeps working while images render, then **attaches** the finished image to the right entity automatically. You choose which image model the agent uses in the chat's **image model** setting; it's saved per community. *** ## Session summary A **session summary** tracks the run: what's been created, the setup-guide progress ring, any web sources the agent cited, and the live queue. Pin it beside the chat or open it from the top bar. *** ## Voice mode Hit the **microphone** button to talk to the agent instead of typing. * **Provider**: ElevenLabs Conversational AI (real-time, bidirectional). * **Auto mode**: the mic stays hot and detects when you've stopped speaking. * **Push-to-talk**: hold the **spacebar** or the PTT button to speak, release to send. ### Shopify embed — voice popup Inside the Shopify admin iframe, the browser's permission policy blocks mic access. Nudj opens a dedicated **voice popup window** with full mic permissions; it talks to the embed over `postMessage`, so transcripts and replies flow back into the main chat. *** ## Model & reasoning The agent runs a single model behind a **Vercel AI Gateway**, so there's no model to pick for the conversation. What you *can* tune is the **reasoning effort** — from none to high — trading speed against depth on harder tasks. The agent uses **MCP (Model Context Protocol)** tools to read and write your Nudj data. Tools are scoped to your RBAC role — the agent cannot escalate its own permissions. *** ## Theming via the agent Ask the agent to: * *"Change the community primary colour to emerald green"* — writes directly to the community theme. * *"Use our display font for headings"* — swaps a theme token. * *"Make the Achievements section feel more premium"* — restyles that widget section. Every change lands in the **live preview**. See [Branding & Appearance](/admin-guide/settings/branding-theming) and [Community Branding](/admin-guide/settings/community-branding). *** ## What the agent won't do * **Launch content without confirmation.** Drafts save, but the agent asks before flipping status to Live. * **Mutate sensitive data without approval.** Destructive actions and Brand Memory writes pause the queue for your confirmation. * **Assign or escalate roles.** Role management is admin-only in settings — not agent territory. * **Act outside your RBAC role.** A Viewer asking the agent to "create a challenge" gets a polite refusal. *** ## Troubleshooting Popup blockers interfere with the voice popup. Allow popups from your Nudj Admin domain (including any custom admin domain your org uses). You're in **Planner** mode. Check your role in **Settings → Team Members** — Managers and above get Builder access. Switch community in the top-nav dropdown, then reopen the chat. The agent picks up the active community from context. The live preview needs the widget configured for the selected community. Work through the widget task in the [Chat Setup Guide](/admin-guide/chat-setup-guide) first. # Dashboard Reference Source: https://docs.nudj.cx/admin-guide/analytics/admin-dashboard Field-by-field reference for the redesigned admin home dashboard — community and org levels The admin dashboard has two levels — community and org — with consistent layout (#1820). This page is the card-by-card reference for what each tile means. ## Community dashboard cards Open `/admin` (after picking a community). ### Engagement | Card | What it shows | | - | - | | Users | Total users with an active-rate indicator | | Activity | Actions performed, challenges done, achievements earned | | Rewards & Points | Points earned, rewards claimed, entries (Rewards-only when points are disabled) | | Completion | Challenge completion rate with a radial gauge | ### Live activity Real-time feed of events: * Member joined * Challenge started / completed * Reward earned / claimed * Achievement unlocked * Leaderboard position change ### Content summary Counts of live content: * **Active Challenges** — live challenges running now * **Live Draws** — live reward draws (draw-type rewards only, not all rewards) * **Coming Next** — scheduled challenges waiting to go live * **Achievements earned** — cumulative achievements earned across the community ### User stats * Total members * Verified vs anonymous * Role breakdown (for admins) * Loyalty tier distribution *** ## Org-level dashboard cards Open `/{orgSlug}` (click the org logo in the top nav). ### Aggregate * Total members across every community * Total active (DAU) * Total challenges live * Total rewards distributed ### Community grid Every community in the org shown as a card: * Community name + banner * Status (Draft / Live / Archived) * Member count * Recent activity * Click-through to that community's dashboard *** ## Consistency between levels Org and community dashboards share the same card patterns (#1820) — same typography, colour coding, spacing (#1854), and icon style. This means teams that manage both don't have to re-learn different layouts. *** ## Light and dark modes All dashboard cards support light and dark (#1173). The cards use theme tokens so your brand colours propagate through every tile. *** ## Drill-down sidebar integration The [drill-down sidebar](/admin-guide/navigation-overview#drill-down-sidebar) (#2216) complements the dashboard — top-level stats on the dashboard, drill into detail in the sidebar. *** ## History & restoration The analytics dashboard had a rough patch where some sections were temporarily removed, then restored in #2145. If you're on a very old build and missing cards, upgrade; they're all back. *** ## API Dashboard data is powered by the analytics tRPC layer (community-level overview/content/activity timeseries, recent activity, and org-level rollups). Rather than duplicate identifiers here, refer to the generated [Analytics API reference](/api-reference/analytics) for the authoritative list of procedures and schemas. # Interpreting Analytics Data Source: https://docs.nudj.cx/admin-guide/analytics/interpreting-data Analytics suite, engagement stats, rewards analytics, v2 tRPC endpoints, caching, pagination, org-level dashboard Nudj's analytics ships as an **analytics suite** — a redesigned dashboard (#1968, #2145) backed by tRPC endpoints (migrated from REST in #1024) with React-Query caching (#1831). ## Where data lives | Data | Source | Notes | | - | - | - | | Dashboard tiles | tRPC `analytics.*` routes | React-Query cached, invalidate on event | | Challenge analytics | `/analytics/challenges/*` v2 | Full CSV export (#1241, #1243) | | Reward analytics | `/analytics/rewards/*` | Allocations, redemptions, distribution, recipients, timeline | | User stats | `/analytics/users/*` | Per-user stats page (#484) | | Org dashboard | `/analytics/organisations/*` | Aggregate across communities (#1701) | *** ## Dashboard surfaces Two levels of dashboards — org and community — share a consistent card layout: * **Engagement** — DAU/WAU, completions, retention. * **Content** — live challenges, rewards, achievements, posts. * **People** — member counts, roles, verified/anonymous. * **Economy** — points earned/spent, XP earned, reward redemption rate. See [Dashboard Overview](/admin-guide/dashboard) for the UI, and [Dashboard Reference](/admin-guide/analytics/admin-dashboard) for a field-by-field card breakdown. *** ## Caching & React Query All analytics hits use React-Query (#1831) with per-query invalidation tags. When you gift a reward, create a challenge, or archive content, the relevant analytics queries auto-refresh — no manual reload. If you hard-refresh and see numbers that jump, the analytics server just finished a pending refresh. Hang tight a few seconds for stability. *** ## Pagination Long result sets paginate (#1033). Sort is persisted across pages. Filter state persists across pagination. *** ## CSV exports For challenges, rewards, and users, export full datasets as CSV: * Participant lists * Raw action submissions (free-text, ratings, image URLs) * Allocation / redemption histories See [Challenge Analytics](/admin-guide/challenges/analytics) and [Reward Analytics](/admin-guide/rewards/analytics) for the specific exports. *** ## Analytics API (custom dashboards) For custom BI pipelines, hit the Analytics API directly. See the [Analytics API reference](/api-reference/analytics) for the full schema of: * Challenge completion funnels * Reward allocation breakdowns * User stats * Org-level aggregations All endpoints support pagination and time-range filtering. *** ## Org-level dashboard The **org dashboard** (`/{orgSlug}`) aggregates stats across every community (#1701). Great for reporting to a single stakeholder when you run multiple communities. See [Dashboard Overview](/admin-guide/dashboard) for the full layout. *** ## Interpreting common signals Members aren't returning. Check recent content — have you shipped something new in the last 14 days? Members earn rewards but don't claim. Audit your redemption flow — too many steps? Codes expired? Too many actions, wrong difficulty, or unclear reward. See [Challenge Analytics](/admin-guide/challenges/analytics#interpreting-the-numbers). # Action Types Overview Source: https://docs.nudj.cx/admin-guide/challenges/action-types-overview Canonical taxonomy of every challenge action — pick the right type for the job Every challenge is a sequence of **actions**. Nudj groups action types into four families — Question, Social Media, Platform, and Rich Media — each with its own dedicated page. ## Pick the right type | Action | Family | When to use | | - | - | - | | Multiple choice | Question | Multi-select answers, quizzes, preference polling | | Single choice | Question | One-pick answers, quick opinions | | Secret code | Question | In-store redemption, event unlocks | | Free text | Question | Open feedback, UGC captions | | Rating / NPS | Question | Satisfaction measurement | | Select value in range | Question | Slider-based quantitative input | | Select from list | Question | Long-list picks (city, SKU) | | Share link | Social | Ask members to share a URL | | Follow | Social | External platform follow CTA | | YouTube watch | Social | Embedded YouTube video consumption | | YouTube subscribe | Social | Subscribe to your channel | | Image upload | Rich Media | UGC, moderation-gated submissions | | Video action | Rich Media | Timestamp-based video interactions | | Object detection | Rich Media | AI-verified photo submissions | | Check-in | Platform | Attendance, daily habits | | Streak | Platform | Consecutive-day rewards | | Game | Platform | Launch a Nudj mini-game | | Complete Achievement | Platform | Gate a challenge behind another | *** ## Where each lives Multiple / single choice, quiz mode, secret codes, free text, rating, NPS, select-from-list, select-value-in-range. Share, follow, YouTube watch, YouTube subscribe. Complete-achievement, check-in, streak, game. Image upload, video action, object detection. *** ## Shared action features All action types share: * **Rich text descriptions** (#672) — headings, lists, links, inline images. * **Banner image upload** above the question/prompt. * **Optional per-action reward distribution** (#1202) — hand out points/XP/assets at action-level in addition to challenge-level. * **Language support** — translate each action's strings. * **Reordering** via drag-and-drop in the challenge editor. *** ## Common patterns 5-10 multiple-choice actions with **quiz mode on**. Reward on high accuracy via a Complete-Achievement gate. Mix of free-text, rating, and single-choice. Export CSV post-event for analysis. Multiple single-use secret-code actions placed around a venue. Sequence of video actions with quiz questions between them; use a Complete-Achievement action to lock later modules. One image-upload action with strict aspect ratio + moderation queue. # Challenge Analytics Source: https://docs.nudj.cx/admin-guide/challenges/analytics Track completion funnel, per-action stats, CSV exports, and filter / sort your data Every challenge has a built-in analytics tab with full stats on attempts, completions, per-action performance, and individual participants. ## The stats tab Open any challenge -> **Insights / Analytics** tab. Attempts -> completed -> rewarded. Shows drop-off between stages so you can see where members stall. For each action: attempts, correct (quiz mode), submission counts, average/NPS (rating). Attempts and completions over time — plot shows when members actually engage. Individual member rows with status, completion %, reward claimed. Challenge stats overview with participant metrics, timeline, and a completion funnel *** ## Challenge list — Completion Rate column The challenges list table now shows a **Completion Rate** column by default (#1821). Sort by it to find your strongest and weakest challenges at a glance. The column is sticky (#782, #921) so it stays visible as you horizontally scroll long tables. *** ## CSV exports Export participant-level data for external analysis (#1241, #1243): * **All participants** for a challenge (status, progress, answers, timestamps) * **Per-action submissions** (raw free-text, ratings, image URLs) * **Aggregate stats** (headline numbers per action) Exports stream from the v2 analytics API (#1296), which replaced the older REST endpoint and is significantly faster on large participant sets (#1274). Open-ended question stats were fixed in #730 — partial submissions no longer distort counts. Re-export any dashboards dated before that PR if you saw unusual numbers. *** ## Filters & sorting The unified filter UI (#1017) sits at the top of every list + stats table: * Status (Draft, Live, Scheduled, Ended, Archived) * Date range (created / started / ended) * Tag * Action type * Campaign **Sorting persists** (#989) across page navigation — set your sort once and it's remembered for the session. *** ## Open-ended questions Free-text action reporting includes: * Submission count * Avg character length * Raw submissions export (CSV) * No sentiment analysis out-of-the-box — pipe the CSV to your own tooling. *** ## Analytics API For custom dashboards, pull challenge stats via the **Analytics API**: * `GET /analytics/challenges/:id/funnel` — completion funnel * `GET /analytics/challenges/:id/participants` — paginated participant list * `GET /analytics/challenges/:id/actions/:actionId` — per-action breakdown See the [Analytics API reference](/api-reference/analytics) for full schemas. *** ## Interpreting the numbers Likely too many actions, wrong difficulty, or unclear reward. Trim actions, preview as a member, test the reward value perception. Members start but don't finish. Look at per-action attempts to find the stalling action and simplify it. Members complete but never claim. Usually a wallet / redemption UX bug — check the reward's redemption config. # Calendar View Source: https://docs.nudj.cx/admin-guide/challenges/calendar-view Visual scheduling for challenges: month view, timezone clarity, Today button, challenge indicators The Calendar View visualises challenges on a month grid — each challenge is a bubble spanning its start → end dates. Essential when you run many overlapping challenges. ## Opening the calendar From the challenges list view, click the **Calendar** tab (#1511). Challenge calendar month view with challenge bubbles and per-day overflow indicators *** ## Navigation * **Today** button (#1150) jumps back to the current week / month. * **Arrows** step back/forward by month. * Click any bubble to open the challenge editor. Calendar design was refined in #1649 — colour-coded by status, challenge indicators, and clearer day boundaries. *** ## Timezone clarity Every date in the calendar shows the **timezone** explicitly (#1505) — avoids the "oh wait, does this go live in my tz or the org's tz?" ambiguity. Default timezone comes from your org settings (see [General Settings](/admin-guide/settings/general)). Individual challenges can override. *** ## Challenge indicators The calendar uses colour-coded markers on each day to show challenge state at a glance: green for start, blue for running, red for end. Hover a day to see which challenges are involved. *** ## Date picker z-index The calendar date picker (and campaign date picker, #1731, #1804) plays nicely with modals — no more z-index fights where the picker hides behind another dialog. *** ## Uses Open calendar, switch to month view, see all scheduled challenges. Look for dead zones (no challenges) and over-packed weeks (3+ overlapping). Adjust. Cross-reference [campaigns](/admin-guide/challenges/campaigns) with the calendar's challenge markers to see which challenges fall inside vs outside a campaign window. Fix the boundaries. Before a big launch, check the calendar for the launch week — avoid launching in a week with 5 other live challenges. *** ## Switching between views Calendar and List share the same filter state. Apply filters on List, switch to Calendar, and the filter persists — useful for "show me only campaign X on the calendar". # Campaigns Source: https://docs.nudj.cx/admin-guide/challenges/campaigns Group related challenges into campaigns with shared metadata and schedule Campaigns are a way to group related challenges — a summer launch, a Black Friday blitz, a Q4 loyalty push — so they share a timeline and appear together in analytics and calendar views. ## Creating a campaign From **Settings -> Organisation -> Campaign Manager** (or via the Admin API `/campaigns` endpoints): Give it a clear, recognisable name — this is what admins see in lists and calendar views. Start and end dates (campaign date-picker z-index fixed in #1731 so it plays nice with modals). Attach challenges to the campaign from each challenge's editor: open the challenge, go to the **Advanced** step, and pick the campaign under **Campaigns**. A challenge can belong to at most one campaign. Campaign goes Live immediately (or Scheduled if start date is future). Create Campaign dialog with campaign name, scheduling, and status fields *** ## Campaigns in the calendar Campaigns show as coloured backdrops in the [Calendar View](/admin-guide/challenges/calendar-view) — all challenges in the campaign display together, making scheduling conflicts visible at a glance. *** ## Filtering by campaign The challenge list (#1155) has a **Campaign** filter in the unified filter drawer. Pick a campaign to see only its challenges. *** ## Analytics Campaign-level analytics show: * Total participants across all challenges in the campaign * Completion funnel aggregated across challenges * Reward distribution summary * Start-to-finish timeline Click any challenge in the campaign to drill into individual analytics. *** ## API * `GET /admin/campaigns` — list * `POST /admin/campaigns` — create * `PATCH /admin/campaigns/:id` — update * `GET /admin/campaigns/:id/challenges` — attached challenges See [Admin API reference](/api-reference/admin). *** ## When to use campaigns Use campaigns for **themed sets** of 3-20 challenges with a shared start/end. Don't use them for organising your content long-term — use [tags](/admin-guide/settings/tags) for that. Run **one live campaign at a time** if possible. Overlapping campaigns splinter member attention. # Challenge Creation Source: https://docs.nudj.cx/admin-guide/challenges/challenge-creation End-to-end walkthrough of the redesigned challenge creation flow Challenges are the core unit of engagement — a set of actions members complete in exchange for points, XP, and rewards. Nudj's creation flow was rebuilt to be a single, focused wizard with preview and draft states. ## Start a challenge Hit the top-nav **+ New -> Challenge**. Nudj opens the **creation screen**, where you pick a starting method: Empty challenge — you fill in every field yourself. Duplicate a challenge that already works, then edit the new draft. Date fields are **excluded** from the copy so you don't accidentally ship with stale schedules (#2142). Browse the template gallery — shared and org-saved templates — and start from a proven setup. Opens the challenge form for a watch-and-answer flow. This button opens a blank form; video timing is enabled separately — see [Video Challenges](/admin-guide/challenges/video-challenges). Blank drafts and templates can also be drafted for you by the Nudj Agent — use **Quick Draft** or **Guided Draft** to have it draft actions and rewards from your goal, then approve, edit, or retry. Challenge creation screen with the four starting-method cards and a template gallery *** ## The wizard After picking a method, you land in the unified challenge wizard with these form steps: Title, description (rich text, up to 3000 chars), image (1:1 ratio 1440x1440), language, tags. * **Featured** toggle highlights the challenge for VIPs. * **Status** starts as Draft. Add actions members must complete. Action types (grouped): * **Question** — multiple choice, single choice with quiz mode, secret code (single-use or multi-use), free text, select-value-in-range, select-from-list, rating/NPS. See [Question Actions](/admin-guide/challenges/question-actions). * **Social Media** — share, follow, YouTube video watch, YouTube subscribe. See [Social Media Actions](/admin-guide/challenges/social-media-actions). * **Platform** — complete-achievement, check-in, streak, game. See [Platform Actions](/admin-guide/challenges/platform-actions). * **Rich Media** — image upload, video action, object detection. See [Rich Media Actions](/admin-guide/challenges/rich-media-actions). See the full [Action Types reference](/admin-guide/challenges/action-types-overview). Attach **rewards**, **points**, and **XP** that members earn on completion. Rewards can be Assets (owned items), Entries (giveaway chances), or both. Each allocation has a **supply limit** so you don't over-issue. See [Completion & Rewards](/admin-guide/challenges/completion-and-rewards) for the Distribution step in depth and the member's end-to-end completion journey. * **Start date** (optional) — leave blank for immediate, set it for future-dated launches. * **End date** (optional) — create urgency, or leave evergreen. * **Progress period** — how long a single attempt can run before it times out. * **Max completions per period** — rate-limit how often each member can complete a repeatable challenge. Future-dated challenges automatically get **Scheduled** status. Nudj blocks you from flipping a future-dated challenge directly to Live (you'd publish into an empty window). See [Calendar View](/admin-guide/challenges/calendar-view). Tags, dark-mode images, [campaign](/admin-guide/challenges/campaigns) grouping, cross-community sync, accent colour, and template settings. (Key-gating via `unlockPrice` is a data field, not an editor control — see [Key-Gated Challenges](/admin-guide/challenges/unlock-keys).) For challenges with manual-review actions (e.g. upload image for approval), configure the review queue. Admin reviewers see pending submissions in the [Review Achievement Flow](/admin-guide/achievements/reviewing-submissions) interface. *** ## Preview before launch Hit **Preview** to open the **preview panel** beside the editor — a live member's-eye view of the challenge with a **desktop / mobile** toggle. Preview respects auth — you see the challenge as a real member would. See [Challenge & Entity Preview](/admin-guide/challenges/preview-mode) for the full preview system. *** ## Publish Status options: | Status | Behaviour | | - | - | | **Draft** | Invisible to members. Edits autosave. | | **Scheduled** | Auto-set when start date is future. Goes Live on that date. | | **Live** | Visible and redeemable. | | **Ended** | Past end date — visible as history, no new completions. | | **Archived** | Admin-only — removed from member view. | You can't publish a challenge with a future start date directly to **Live** — Nudj forces the **Scheduled** status so your action log stays accurate. (#1142) *** ## Copy and iterate On any challenge row in the list view, use the **copy** action to duplicate. All content copies; date fields are cleared so you can pick a new schedule. See [Copy / Duplicate Items](/admin-guide/settings/duplicate-and-copy). *** ## What changed | Change | Why | | - | - | | Single unified wizard (vs. old 2-screen wizard) | Fewer context switches, clearer step progression (#1186) | | Creation-method modal | Makes duplicate and AI-drafted challenges first-class (#2297) | | Preview panel | See the member view with a desktop / mobile toggle (#1942) | | Future-dated Scheduled status | Prevents accidental publish into empty windows (#1142) | | Copy-an-item with date exclusion | Templates can be reused without stale dates (#1412, #2142) | | Unified + New button | Consistent creation entry point across the admin (#2409) | *** ## Next steps The full taxonomy of actions available. Group related challenges under a campaign. The preview panel and auth-respecting walkthroughs. Completion funnel, per-action stats, CSV export. # Challenge Dos and Donts Source: https://docs.nudj.cx/admin-guide/challenges/challenge-dos-and-donts Best practices and common pitfalls to avoid when creating challenges The difference between challenges that crush it and ones that flop isn't luck—it's knowing what to do and what to avoid. *** ## The Golden Rules Ask "What do I want users to do?" Define the behavior first. Points follow naturally. Don't think "award 100 points" first. You'll make arbitrary decisions that feel unfair. Each action past 5 reduces completion by 10-15%. Short challenges feel like quests. 8+ actions tanks completion rates. Users abandon challenges that feel like homework. Knowledge OR Community OR Creative OR Social. One per challenge. Sequence them for variety. Users get confused jumping between different challenge types. Creates cognitive overload. 1 point per 5 seconds. Users intuitively feel it's fair. Stop overthinking. Random points feel arbitrary and demotivating. Inconsistency tanks future engagement. *** ## Titles & Descriptions | DO | DON'T | | - | - | | **"Learn Product Basics in 5 Min"** — Specific, time-bounded, action-oriented | **"Product Knowledge"** — Generic, vague, doesn't say what they'll do | | **"Connect With 3 Members"** — Clear goal, easy to understand | **"Community Engagement"** — What does "engagement" even mean? | | **"Share Your Biggest Win"** — Emotional hook, personal relevance | **"Share a Story"** — Could be anything | | Description: 1-2 punchy sentences about what they'll do and why it matters | Description: 3+ paragraphs of backstory nobody reads | *** ## Difficulty Levels | Level | DO | DON'T | | - | - | - | | **Beginner** | \< 5 min, no setup, immediately actionable | Label as Beginner but requires 20 min. Users feel misled | | **Intermediate** | 5-15 min, some context needed, moderate effort | Vague time estimates. Set expectations clearly | | **Advanced** | 15-30+ min, prerequisites, significant commitment | Make it harder than you say. Users lose trust | *** ## Testing & Launch | DO | DON'T | | - | - | | Preview as a user BEFORE launch | Skip testing because "it looks fine in the Control Room" | | Complete it yourself and time it | Assume it takes less time than it actually does | | Test on mobile | Launch and hope it works on phones | | Verify rewards actually award | Find out after launch that points weren't given | | Monitor the first 24-48 hours | Set it and forget it for a week | | Edit if drop-off is >50% at one action | Ignore high drop-off rates and blame users | *** ## Rewards Strategy | DO | DON'T | | - | - | | **Mix types**: Points + XP + Badges works | **Just Points**: Boring, no sense of achievement | | **Overdeliver on first challenge**: Hook them early | **Underdeliver**: Cheap rewards kill motivation | | **Match reward to effort**: 5 min = 50-75 pts | **Reward for free**: Make it feel valueless | | **Add XP if you have leveling**: Feeds progression | **Forget progression**: Points only feel empty | | **Use Badges for milestones**: "First 100 Points" hits different | **No recognition**: Users don't feel their wins | *** ## Timing & Scheduling | DO | DON'T | | - | - | | **Set end dates for urgency**: 7-14 day windows work | **Evergreen everything**: No sense of scarcity or urgency | | **Schedule for campaigns**: Announce, then go live | **Go live immediately**: No anticipation or buildup | | **Allow repeats strategically**: Daily challenges keep users coming back | **Allow unlimited repeats**: Becomes grinding, feels empty | | **Cap completions per period**: 1/day feels like a routine | **Unlimited completions**: Users farm it, then abandon | *** ## After Launch: Iteration | DO | DON'T | | - | - | | **Check analytics day 1-2** | Wait a month to review performance | | **Edit unclear instructions** | Leave confusing copy because "users should understand" | | **Simplify actions if drop-off is high** | Blame users for not trying hard enough | | **Ask users what they loved** | Assume you know what worked | | **Iterate based on real feedback** | Never update—set it once and ignore it | | **Kill challenges that don't work** | Keep launching bad challenges and wondering why | *** ## Pro Tip: The Sequence Strategy Instead of one mega-challenge: 1. **Create 3-4 related challenges**, each focused on one format 2. **Space them a few days apart** for sustained engagement 3. **Build on each other**: "Learn" → "Teach" → "Create" → "Share" 4. **Same reward formula per challenge** so it feels fair This creates a *journey*, not a checklist. Users complete sequences. They abandon single long challenges. *** ## Quick Checklist Before Launch Can you answer "What do I want users to do?" in one sentence? Count your actions. Is it 5 or fewer? Knowledge, Community, Creative, or Social—just one? Did you use the 1 point per 5 seconds formula? Does your title answer "What will I do?" You actually opened it on a phone, right? Completed it yourself and confirmed points/badges awarded? Do you have time to check analytics in the first 24-48 hours? *** ## Next Steps * **[Challenge Creation Guide](/admin-guide/challenges/challenge-creation)** — Step-by-step walkthrough * **[Analytics](/admin-guide/challenges/analytics)** — Measure performance and iterate * **[Management](/admin-guide/challenges/management)** — Pause, archive, and optimize active challenges # Challenge Formats Source: https://docs.nudj.cx/admin-guide/challenges/challenge-formats Choosing the right challenge format for your engagement goals and strategy Challenges come in four distinct flavors, each designed for different engagement outcomes. Pick the right format and your community will naturally engage the way you intended. Pick wrong, and you'll be fighting against the mechanics. ## The Four Challenge Formats Quiz-based challenges that teach, verify understanding, and certify competency. Perfect for onboarding, training, and education. Activities that drive interaction within your platform. Great for building relationships and engagement. User-generated content campaigns where members create and share. Ideal for showcasing community talent. External sharing and amplification on social platforms. Perfect for viral campaigns and brand advocacy. *** ## Knowledge Challenges Teach something, test understanding, certify learning. People prove they know their stuff. **Example: Product Training Challenge** 1. Watch embedded training video (Rich Media) 2. Answer quiz questions (Multiple Choice, Specific Answers) 3. Earn badge/certificate on completion **Example: Certification Program** 1. Read learning materials (External Links) 2. Complete assessment (Multiple Choice with correct answers required) 3. Pass with 80%+ to unlock certificate **Example: Onboarding Knowledge Check** 1. Quick video intro (Rich Media) 2. True/False warm-up (2-option Multiple Choice) 3. Specific answer questions about key concepts 4. Final multiple choice assessment Completion rates vary: mandatory certifications hit 65-85%, voluntary learning hits 40-50%. The difference? One you *have* to do, the other you *want* to do. Knowledge challenges work best when the content is actually valuable to users, not just compliance theater. People can smell the difference. *** ## Community Challenges Pull members into interaction with each other. Someone posts, another comments, a third reacts—suddenly there's momentum and belonging. These challenges weave people together. **Example: Onboarding & Connection** 1. Introduce yourself in a post (Platform: Create Post) 2. Comment thoughtfully on 2 other introductions (Platform: Comment) 3. Like 5 posts from the week (Platform: Like) 4. Optional: Recommend a new member to connect with (manual or automated) **Example: Peer Learning & Help** 1. Share something you learned recently (Platform: Create Post) 2. Help answer someone's question (Platform: Comment) 3. React with encouragement to 3 others (Platform: Like) **Example: Feedback & Listening** 1. Answer a community poll (Platform: Vote) 2. Comment with your perspective (Platform: Comment) 3. Read responses from others and react (Platform: Like) Easy social challenges hit 50-70% completion. Ones requiring more effort drop to 30-45%. Frame participation as helping, not as tasks. People move mountains for community but resist busywork. Community challenges work best when they feel natural, not forced. "Help someone solve a problem" beats "participate in discussion." *** ## Creative Challenges Ask members to make something and share it. A photo, video, design, testimonial—something that required their hands, brain, or voice. These create ownership and pride. **Example: User Testimonial Campaign** 1. Record a short video: "Why I love \[product]" (Rich Media: Video Upload) 2. Optionally share on LinkedIn (Social: LinkedIn Share) 3. Optional: Vote on favorite testimonials (Platform: Vote on submitted content) **Example: Photo Contest** 1. Take a photo related to your community (Rich Media: Image Upload) 2. Rate & comment on 3 other entries (Platform: Comment & Rate) 3. Top 5 get featured + bonus XP **Example: Creative Showcase & Feedback** 1. Submit your creative work - design, art, writing, etc. (Rich Media: File Upload) 2. Leave constructive feedback on 2 submissions (Platform: Comment) 3. React with appreciation to 5 pieces (Platform: Like) Simple uploads hit 40-60% completion. Requiring real creativity drops to 25-40%. Specific, inspiring prompts beat generic ones. "Share a photo of how you use our product in your daily life" gets way more engagement than "share anything related to our brand." The prompt is everything. Specific, inspiring prompts get better responses than generic "share anything" requests. *** ## Social Challenges Ask members to share on their personal social accounts—Twitter, LinkedIn, TikTok, wherever they already live. You're borrowing their reach and their credibility with their networks. **Example: Product Launch Amplification** 1. Share the launch announcement on LinkedIn (Social: LinkedIn Share) 2. Optional: Tweet about it (Social: Twitter Share) 3. Optional: Share on Instagram Stories (Social: Instagram Share) **Example: Brand Advocacy** 1. Share a customer success story (Social: LinkedIn Share) 2. Personalization: Add your own experience (optional text) 3. Complete when shared and visible **Example: Event Promotion** 1. Share the event link on Twitter (Social: Twitter Share) 2. Tag 2 colleagues who should attend (Social: Twitter Share with mentions) 3. Optional: Share on LinkedIn for broader reach (Social: LinkedIn Share) **Example: Thought Leadership** 1. Share an article from your blog (Social: LinkedIn Share) 2. Add personal commentary (optional text field) 3. Complete when posted Completion rates are naturally lower (20-40% for easy valuable shares, 5-15% if it requires effort or feels risky) because members must leave your platform. But that's exactly why it works—they're sharing with *their* people who *trust* them, not marketing messages. Make the share valuable for *them*, not just for you. "Share this article about industry trends" gets traction because it helps their professional reputation. "Promote our company" doesn't. Your members' credibility is on the line. Make sure whatever they're sharing is actually valuable to *their* audience, not just yours. *** ## Comparing the Four Formats Here's a quick reference showing how these formats compare: | Format | Best Use Case | Time Investment | Completion Rate | User Motivation | | - | - | - | - | - | | Knowledge | Education, certification | 5-20 min | 40-85% | Learning, competency | | Community | Relationships, culture | 5-15 min | 30-70% | Helping others, belonging | | Creative | Showcase, content | 10-30 min | 25-60% | Expression, recognition | | Social | Reach, advocacy | 2-5 min | 5-40% | Amplification, visibility | *** ## How to Choose Your Format The decision is usually obvious once you know what you actually want to happen. Teaching? Knowledge. Relationship building? Community. Getting member content? Creative. Reaching beyond your platform? Social. That's it. If you're torn between two, they probably layer well together anyway. *** ## Pro Strategies for Combining Formats **Layer formats for depth.** Knowledge + Community is powerful: "Learn this skill, then help someone else learn it." Learning gets verified *and* reinforced through teaching. Engagement doubles because each format feeds the other. **Stack them over time.** Social → Knowledge → Community → Creative is a natural progression. Start easy (share our launch on LinkedIn), build involvement (learn how to use the product), deepen relationships (help onboard new members), then celebrate (show what you built with it). People move through progressively, each step earning the next. **Different audiences, different formats.** New members respond to Community (belonging) + Knowledge (context). Active members are ready for Creative (express themselves) + Social (advocate). Power users can handle all formats with increasing complexity. *** ## Next Steps Ready to build a challenge? Here's the path forward: 1. **Determine Your Format** — You're here! Pick which format matches your goal 2. **[Explore Action Types](/admin-guide/challenges/action-types-overview)** — See what specific actions fit your format 3. **[Learn Strategy](/admin-guide/challenges/challenge-strategy)** — Understand patterns that work 4. **[Create Your Challenge](/admin-guide/challenges/challenge-creation)** — Build it step-by-step # Challenge Strategy Source: https://docs.nudj.cx/admin-guide/challenges/challenge-strategy Holistic thinking about challenge design, patterns that work, and common pitfalls Building good challenges isn't about feature complexity—it's about solving real problems for your community. This guide covers the patterns that work, why they work, and how to think about challenges strategically. ## The Core Principle Every challenge should answer this question: **"Why would someone WANT to complete this?"** If the answer is "because they get points," you're building gamification theater. If the answer is "because it helps them learn," "connects them with peers," or "solves a real problem," you're building engagement. The best challenges don't feel like challenges at all—they feel like normal community activities that happen to award points. The points are the reward, not the motivation. *** ## Common Patterns That Work Five proven patterns that consistently drive engagement and completion. Think of these like migration routes—birds don't invent new paths, they follow proven ones because they work: ### Pattern 1: Learning Progression Teaching something works best when you hit it from multiple angles: Watch it → Prove you understand it → Teach someone else. Each step reinforces the last. Watch/read - Introduce the concept with media or text Quiz - Prove understanding through questions Help someone - Reinforce by teaching others Certification - Formal validation of mastery **Real examples**: Employee onboarding with training video, quiz, then answering new hire questions. Product launch with demo, feature quiz, then recommending to customers. **Why it works**: Learning sticks when you experience it multiple ways. **Completion rates: 60-80% mandatory, 40-50% voluntary.** *** ### Pattern 2: Community Building Get people doing community activities together, and they'll keep doing them after the challenge ends. The activity itself is the point—the reward is secondary. People are pack animals—they'll bond over shared activities even if there's no formal reward. Comment, vote, or post - Do something community-focused Share externally - Amplify it beyond the platform Get acknowledged - Others celebrate the contribution **Real examples**: "Comment on 3 discussions then share highlights on Slack." "Help someone who's stuck then share a tip." "Vote on features then explain your reasoning." **Why it works**: People like being helpful but need a nudge. Challenges create that nudge while building genuine community habits. **Completion rates: 45-65%** (varies by community) *** ### Pattern 3: Behavior Change New behaviors stick when they're simple, repeated, and rewarded. Most people won't adopt something hard on the first try. Explain why - Set the stage for why this matters Do the simple thing - 1-2 steps max, achievable Daily or weekly recurring - Build the habit through repetition **Real examples**: New feature adoption (intro → use daily for a week). Daily standups (morning update challenges). Security (password update → 2FA enablement). **Why it works**: Habits don't form in a day. Repetition builds them. **Completion rates: 30-50%** (habits are hard, but repetition helps) *** ### Pattern 4: Competitive Fun Competition creates urgency and keeps people engaged. Add a leaderboard, timer, or scores—suddenly people care. Challenge with visible point system and rules Make rankings visible - Show who's winning Add deadline - Daily, weekly, or seasonal window **Real examples**: Weekly trivia with rankings. Prediction leagues ("guess adoption rates"). Quick-time challenges where speed matters as much as accuracy. **Why it works**: Competition taps into natural drive to win. **Completion rates: 40-70%** (varies wildly by how competitive your community is) *** ### Pattern 5: Milestone Recognition Celebrate achievements and create a visible path to mastery. Group challenges into a journey with clear progression Each completion unlocks the next step Special badges or celebrations at key milestones Show users the complete journey they're on **Real progression examples**: * Certification program: "Level 1: Basics" → "Level 2: Intermediate" → "Level 3: Expert" * Onboarding journey: "Day 1: Welcome" → "Week 1: First contribution" → "Month 1: Community member" * Mastery path: "Learn feature" → "Use feature" → "Teach feature" **Why it works**: Progression gives people a visible path to mastery. **Completion rate: 60-75%** (clear progress significantly increases motivation) *** ## Design Principles Core principles that make challenges actually work: Single challenge = 3-5 actions max. Complex challenges feel like checklists. Users prefer 5 quick challenges over 1 massive one. Time investment should match reward. 2-minute challenge = 50 points feels right. 20-minute challenge = 50 points feels cheap. People resent wasted time more than they appreciate rewards. "Learn why this matters" before "Do this task." Context makes actions meaningful. Rich Media opening (video or text) explaining the "why" increases completion 20-30%. Too easy = boring, abandoned. Too hard = frustrating, abandoned. Sweet spot: "I need to think a little but I can succeed." Show results immediately (not "submitted, we'll grade this later"). Explain correct answers (especially on quizzes). Celebrate completion explicitly. *** ## Common Pitfalls Five mistakes that kill challenge effectiveness: **Problem**: Making challenges for the sake of points, not solving a real problem. **Example**: "Award 100 points just for logging in daily" **Result**: Feels fake, creates points without value, people stop caring **Fix**: Ask what behavior you actually want. If it's engagement, make challenges about activities (learning, collaborating) that create value. **Problem**: One huge challenge instead of a series of smaller ones. **Example**: "Complete a 20-step onboarding checklist" **Result**: 70% abandonment rate, people overwhelmed **Fix**: Break into 3-5 separate shorter challenges, show progress between them. **Problem**: Users don't know what "success" looks like. **Example**: "Help someone in the community" (too vague) **Result**: People confused about what counts, don't complete it **Fix**: Be specific: "Leave a helpful comment on someone's question." **Problem**: Reward doesn't fit the effort. **Example**: "Submit a 500-word essay for 50 points" (should be 200+) **Result**: Feels unfair, kills motivation **Fix**: Think about time → points ratio. 2-3 minutes per 50 points is reasonable. **Problem**: You create one amazing challenge, then nothing. **Result**: Engagement spikes once, then drops off **Fix**: Build recurring challenges or challenge series that keep people coming back. *** ## Measuring Success Of people who start, how many finish? Target: >60% indicates good difficulty calibration Of people who complete, how many do it again? Indicates real value and engagement Did this challenge increase overall platform activity? Compare activity before/after How long does it actually take? Does it match your estimate and difficulty level? **Metrics to Ignore**: * Total points awarded (meaningless without context) * Number of challenges created (quantity ≠ quality) * Raw participation numbers (doesn't measure depth) **Red Flag**: If completion rate drops below 40%, the challenge is probably too hard or unclear. Try simpler instructions or add more context first. *** ## Challenge Lifecycle Like herding cats—you need to start small with a tight group, watch for what works, then scale up: Test with a small group first. Watch completion rates and feedback. Adjust difficulty/clarity based on early data. Review analytics 48-72 hours after launch. Check "where did people drop off?" Improve unclear instructions or reduce overscoped sections. Make changes based on data. Re-launch with improvements. Repeat. If completion rate drops below 30% after 2+ launches, consider retiring. But test improvements first—sometimes small tweaks resurrect challenges. *** ## Strategic Questions to Ask Before creating a challenge, answer these 7 questions: If the answer is "to give points," rethink it. Real challenges solve community problems. If it's not clear, make it clearer in the description/first action. The motivation needs to be obvious. Estimate time, compare to reward, adjust if needed. Time-reward mismatch kills engagement. Learning? Collaboration? Adoption? Engagement? Be specific. Vague goals lead to vague challenges. Completion rate? Repeat rate? Follow-up behavior? Know your success metric upfront. Newbies? Power users? Specific group? Tailor difficulty and content to your audience. One-time challenge, or part of a series? Plan for follow-up engagement. *** ## Next Steps * **[Challenge Formats](/admin-guide/challenges/challenge-formats)** — See the 4 main challenge types * **[Action Types](/admin-guide/challenges/action-types-overview)** — Understand what building blocks you have * **[Challenge Creation](/admin-guide/challenges/challenge-creation)** — Create your first challenge * **[Analytics](/admin-guide/challenges/analytics)** — Measure and iterate based on data *** **Remember**: The best challenge is the one your community actually completes and talks about afterward. Keep it simple, make it matter, and let the data guide your next move. # Completion & Rewards Source: https://docs.nudj.cx/admin-guide/challenges/completion-and-rewards The Distribution step explained, plus what a member sees end-to-end when they finish a challenge The [creation wizard](/admin-guide/challenges/challenge-creation) gets a challenge built. This page is about the part that makes members come back: **what they earn, and what they actually see when they earn it.** It covers the **Distribution** step in depth, then walks the member's completion journey from the final action to the reward landing in their wallet. **Points**, **XP**, and **reward assets** all feed the same member wallet, no matter which surface earned them. See [Earning Rewards](/user-guide/rewards/earning) for the full list of earning surfaces. ## Two places rewards attach A challenge can pay out at two levels, and they stack: Each **action** inside the challenge can carry its own optional points / XP / reward allocation. A member who completes that action is credited immediately, even mid-challenge. Completing the **final action** triggers the challenge-level payout: the "completion bonus". This is what you configure in the **Distribution** step. Use per-action rewards to keep momentum inside a long challenge, and the challenge-level bonus as the headline payout for crossing the finish line. A common pattern: small XP per action, then a reward asset on completion. *** ## The Distribution step In the wizard's Distribution step (labeled **Rewards** in the editor) you attach points and XP plus optional reward assets. ### Points and XP | Currency | What it does | Member-facing reference | | - | - | - | | **Points** | Spendable balance: redeemed in the shop, on giveaway entries, or for multiplier boosts. | [Points](/user-guide/rewards/points) | | **XP** | Progression / status: drives levels and leaderboards, not spent. | [XP](/user-guide/rewards/xp) | Set the amount of each a member can earn on completion; either can be zero. The Points value is the **maximum** a member can earn: unless your organisation has **Fixed Points Distribution** enabled, challenge-completion points are pro-rated by how much of the challenge the member actually completed, so a partial or scored completion earns proportionally fewer points. With Fixed Points Distribution on, every completion grants the full amount. Points are **not** the same as **keys**. [Key-gated challenges](/admin-guide/challenges/unlock-keys) are unlocked by spending a separate keys balance (`unlockPrice`), which members earn from specific challenges, rewards, or admin gifts. A points payout does not let a member open a key-gated challenge. Rewards step showing the Points and Bonus XP amount fields with a per-player total ### Reward assets Beyond points and XP you can attach a catalog **reward** to the completion. Rewards come in two allocation types (a single reward can use both): Owned items the member keeps: a voucher, a digital collectible, a physical item, or a multiplier boost. Lands in the wallet on completion. Chances in a giveaway / prize draw. Completing the challenge adds the member's entries to the draw rather than handing them an item outright. See [Reward Types](/admin-guide/rewards/reward-types) for the full catalog of what an asset can be and how to configure it. Select a Reward dialog listing catalog rewards with Assets allocation type and supply ### Supply: what this challenge hands out In the **Distribution** step you pick a reward, set the **amount to distribute**, and set a **Supply** cap for *this* allocation: how many of the reward this challenge can give out before it stops. That cap can't exceed the reward's own remaining inventory. Per-member limits and the reward's overall inventory are a separate, reward-level concern: | Set on… | Control | Question it answers | | - | - | - | | **The challenge** (Distribution step) | Amount to distribute, and this allocation's **Supply** | How many of the reward does *this challenge* hand out? | | **The reward** (reward form) | Total inventory, and **per-member caps** (`Max assets per user` / `Max entries per user`) | How many exist in total, and how many can a *single member* hold? | Each can be a finite number or left **unlimited**. Once a reward's inventory is exhausted it stops being granted; later finishers still get the points and XP, just not the capped asset. The reward's **total inventory** and **per-member caps** live on the **reward itself**, set them when you create or edit the reward, not on the challenge allocation. Inventory can also be tracked as a single shared pool across every challenge that hands the reward out. The challenge's Distribution step only decides how much of that inventory to give out here. See [Reward Types](/admin-guide/rewards/reward-types). Anything with a finite supply is genuinely scarce: once it's gone, it's gone for the live audience. Double-check the supply you're handing out here, and the reward's own inventory and per-member caps, before you go Live, especially on a high-traffic challenge. [Preview](/admin-guide/challenges/preview-mode) the challenge to confirm. Reward pool with amount and supply columns and All/Random/Priority distribution mode tabs *** ## Earn-once vs repeatable Whether a member can earn a challenge's rewards once or on a recurring basis is governed by the **Scheduling** step, not Distribution: * **Progress period**: how long a single attempt may run before it times out. * **Max completions per period**: how often each member can complete a *repeatable* challenge and be credited again. Leave max-completions at one for a classic earn-once challenge (onboarding, a one-time survey). Raise it for habit loops (a daily check-in, a weekly quiz) where you *want* members earning the payout repeatedly. See [Challenge Creation → Scheduling](/admin-guide/challenges/challenge-creation). *** ## What the member sees Here's the same flow from the member's side, end to end. The member works through the challenge's actions. Any action carrying a per-action allocation credits them the moment it's completed, and they see the points / XP tick up. Completing the last action triggers the challenge-level distribution. The member sees a **completion screen** summarising what they just earned: points, XP, and any reward asset. Member challenge-completion celebration screen The balances update immediately. Points become spendable; XP advances levels and [leaderboard](/user-guide/leaderboards) standing. If the member has an active **XP Boost** or **Points Boost**, the earning is multiplied for that currency (highest active multiplier per type wins). * **Assets** appear in the member's wallet, ready to use or [redeem](/user-guide/rewards/redeeming). * **Entries** are added to the relevant giveaway draw. Member wallet showing coins balance, badges, and an unredeemed reward asset Members can revisit their completed challenges and earnings. See [Participation](/user-guide/challenges/participation) and the post-challenge [Recap](/user-guide/challenges/recap). *** ## Verify it before you ship 1. **Preview** the challenge ([preview panel](/admin-guide/challenges/preview-mode)) and walk it as a member would, then confirm the completion screen shows the payout you intended. 2. Check the **supply** this challenge hands out (and the reward's own inventory and per-member caps) are set deliberately, not left unlimited by accident on a scarce reward. 3. After launch, watch the **completion funnel** in [Analytics](/admin-guide/challenges/analytics): a healthy Completions number means the payout is landing. *** ## Next steps The full wizard, including where Distribution sits. Every surface that feeds the wallet, not just challenges. What members do with what they've earned. Track the completion funnel and per-action stats. # Challenge Management Source: https://docs.nudj.cx/admin-guide/challenges/management List view, calendar view, filter, sort, copy, and campaign grouping for challenges at scale The challenge list page is your daily driver for managing dozens or hundreds of challenges. It was rebuilt around three patterns: a redesigned table with sticky columns, a unified filter drawer, and a calendar view for scheduling. ## List view The default view is a redesigned **table** (#1155): * **Sticky columns** (#782, #921) — title and key action buttons stay visible as you horizontally scroll. * **Persisted sort** (#989) — once you pick a sort, it's remembered for the session. * **Completion rate column** visible by default (see [Challenge Analytics](/admin-guide/challenges/analytics)). * Status badges use theme-aware colours — success (live), warning (ending soon), destructive (expired), primary (scheduled). Challenge list table with status badges and a completion-rate column ### Unified filter drawer Click **Filters** to open the drawer (#1017, #1507, #1523, #1540). Available filters: * **Status** — Draft / Live / Scheduled / Ended / Archived * **Date range** — created / start / end * **Tag** — any applied tags * **Action type** — filter by what's inside * **Campaign** — show only challenges in a specific campaign Filters persist across pagination. *** ## Calendar view Switch to **Calendar view** (#1511) to see challenges on a month grid. Each challenge appears as a bubble spanning its start -> end dates. Useful for: * Spotting overlap / dead weeks * Planning campaign timelines * Scheduling around holidays See [Calendar View](/admin-guide/challenges/calendar-view) for the full feature. *** ## Campaigns Group related challenges into a **campaign** (#1127) — e.g. "Summer Launch", "Q4 Loyalty Push". Campaign members share start/end windows and show up together in the calendar. See [Campaigns](/admin-guide/challenges/campaigns) for setup. *** ## Copy-an-item On any challenge row, the row menu has **Duplicate** (#1412). It clones the entire challenge — actions, rewards, advanced settings — but **excludes date fields** (#2142) so you don't accidentally relaunch a past event. Picks a new name suffix ("(Copy)") by default. See [Copy / Duplicate Items](/admin-guide/settings/duplicate-and-copy). *** ## Bulk edit & archive Select multiple rows and bulk: * Archive (moves status to Archived) * Add / remove tags * Export (CSV of selected challenges + stats) Bulk archive is reversible — status flips to Archived, but nothing is deleted. Use the Archived filter to find them later. *** ## Tips When your challenge count crosses \~20, switch to Calendar view for planning and back to List for day-to-day operations. The two views share the same filter state. Use **campaign grouping** early — retroactively grouping 100 challenges into campaigns is painful. # Challenges Overview Source: https://docs.nudj.cx/admin-guide/challenges/overview Drive user behavior with gamified tasks and activities # Engagement Challenges **Challenges** are the heartbeat of Nudj. They are the specific tasks you want your users to perform—from simple surveys to complex multi-step social media campaigns. When a user completes a challenge, they are rewarded with Points, XP, and potentially Badges or tangible items. **The Goal**: Move users from passive observation to active participation. **The Reward**: Instant gratification through Nudj's automated reward system. ## How a Challenge is Structured Every challenge consists of three parts: A title and image that makes the user want to click. *(See [Content Best Practices](/admin-guide/engagement/content-best-practices))* The actual steps the user must take (e.g., "Watch video", "Answer quiz", "Share on X"). The Points, XP, or Rewards the user receives instantly upon completion. *(See [Completion & Rewards](/admin-guide/challenges/completion-and-rewards))* ## The Two Paths to Creation Nudj offers two ways to build challenges, depending on your creative energy: ### 1. The AI Agent Path (Recommended) Don't know what to build? Ask Nudj AI. * *"Create a 5-day onboarding streak for new users."* * *"Draft 3 challenges about our new product launch."* The AI will generate the titles, find appropriate images, and configure the actions for you. *(Learn more about [Nudj AI](/admin-guide/ai-agent))* ### 2. The Manual Builder Full control over every pixel and point. 1. Go to **Engagement > Challenges > Create New**. 2. Choose from our [Action Templates](#action-types). 3. Configure your logic and rewards. ## Action Types: What can users do? We offer a wide range of "Actions" that can be combined into a single challenge: | Action Category | Examples | Best For... | | - | - | - | | **Feedback** | Quizzes, Polls, Surveys | Learning about your audience. | | **Education** | Watch Video, Read Article | Product training and onboarding. | | **Growth** | Share on X/Twitter, Refer a Friend | Driving new signups and awareness. | | **Community** | Post in Feed, Join Channel | Building "social glue" between users. | | **Verification** | File Upload, URL Submission | Verifying real-world tasks. | ## Strategic Challenge Design ### The "First Win" Principle Your first challenge should be impossible to fail. A "Welcome to the Community" challenge that rewards users just for clicking a button builds immediate positive momentum. ### Multi-Step Challenges Break big tasks into small bites. Instead of one long challenge, create 3 short ones. Users are more likely to finish three 1-minute tasks than one 3-minute task. ### Scheduling & Expiry * **Limited Time**: Create urgency by making a challenge expire in 48 hours. * **Scheduled Drops**: Build anticipation by scheduling challenges to go live every Monday morning. ## Managing Your Challenges In the **Challenges List**, you can track your success in real-time: * **Views**: How many people saw the "Hook"? * **Starts**: How many people clicked in? * **Completions**: How many people crossed the finish line? **Pro Move**: If a challenge has high Views but low Starts, your title/image is good, but the "Hook" description isn't convincing enough. *** **Ready to build?** [Create your first challenge now](/admin-guide/challenges/challenge-creation). # Platform Actions Source: https://docs.nudj.cx/admin-guide/challenges/platform-actions Native Nudj actions: complete-achievement, check-in, streak, game Platform actions link your challenge to other Nudj features — achievements, streaks, games, check-ins. They're the glue for building progression loops. ## Action types ### Complete Achievement This is the **achievements as actions** pattern introduced in #729. It lets you gate a challenge behind an achievement — e.g. "To start this challenge, you must have earned the First 10 Challenges achievement". Add the action, pick an existing achievement, and the challenge won't start until the member has unlocked that achievement. Great for building tiered progressions. See [Achievements as Actions](/admin-guide/achievements/achievements-as-actions). ### Check-In Members tap a "check in" button to mark attendance. Optionally enable the **streak** option (#1214) so consecutive check-ins increment a streak counter. ### Streak Reward members who maintain a streak (daily, weekly). Paired with [Streaks & Mastery](/admin-guide/gamification/streaks-mastery). ### Game Launch a Nudj **game** (flick, catch, memory, basketball, etc.) as an action. Configured via the [Games](/admin-guide/gamification/games) system. Reward allocations on game actions are resolved through the game's reward config (#1071, #1034). Play Game action with the game type picker and game config selector *** ## Reward distribution on actions Actions can carry their **own** reward allocation in addition to the challenge-level reward (#1202). Useful when you want to: * Reward every checkpoint along a long challenge. * Hand out instant assets on a game action (vs. only at challenge completion). * Mix currency types (points on action 1, XP on action 2, asset on completion). Configure per-action distribution in the action edit sidebar during challenge creation. *** ## Examples One challenge with a single check-in action (streak enabled). Evergreen, no end date. Rewards tiered at 7-day, 30-day, 100-day milestones via achievements. Five challenges, each fronted by a Complete-Achievement action pointing at the previous challenge's completion achievement. Members finish them in order. Challenge with a single Game action (Flick game). High-score triggers a reward allocation via [game reward config](/admin-guide/gamification/games). # Challenge & Entity Preview Source: https://docs.nudj.cx/admin-guide/challenges/preview-mode Preview panel with desktop/mobile toggle, preview-in-new-tab, preview auth, chat previews Nudj's preview mode shows any challenge, reward, post, or achievement as members see it — before you go live. Every entity supports preview (#898). ## The preview panel Click **Preview** on any entity editor to open the **preview panel** (#1942). It renders the member-facing experience beside your editor, with a device toggle: Member-facing web app rendering at a standard desktop width. Toggle to mobile width — catch responsive layout bugs before launch. Your admin editor stays visible alongside the panel, so you can edit and watch the preview update. *** ## Preview in new tab Click **Preview in New Tab** (#703) to open just the member view in a new browser tab. Useful for: * Sending a preview link to a teammate for review (before going Live). * Doing a quick mobile spot-check on your phone. *** ## Preview respects auth Preview is auth-aware (#2038) — you see the challenge / reward as a real authenticated member would. This means: * Key-gated challenges show the unlock prompt. * Achievement-gated challenges show the lock screen if you haven't unlocked. * Loyalty-tier-scoped rewards show according to your current tier. The preview button itself was fixed in #749 (earlier versions sometimes failed to route correctly). *** ## Chat previews The Nudj Agent supports **chat previews** (#900) — when the agent drafts content, it shows an inline preview card so you can see what the agent proposes without leaving the chat. *** ## What you can preview Every admin-authored entity: * Challenges (most commonly previewed) * Rewards (wallet row, asset card, redemption flow) * Posts (feed card + full post) * Achievements (profile badge + criteria view) * Games (via the game builder preview, see [Games](/admin-guide/gamification/games)) * Leaderboards (tier display, distribution) * Themes (live iframe preview, see [Branding](/admin-guide/settings/branding-theming)) *** ## Best practices **Always preview before Live.** Do a full end-to-end run of the challenge — complete it, claim the reward — in preview mode before flipping status. Test previews **on mobile**. Most members use mobile. Use the mobile preview toggle or preview-in-new-tab on your phone. # Question Actions Source: https://docs.nudj.cx/admin-guide/challenges/question-actions Every question-style action: multiple choice, single choice, quiz mode, secret codes, free text, ratings, select-from-list, select-value-in-range Question actions collect structured input from members. They're the most flexible action family — ranging from quick one-taps (single choice) to long-form feedback (free text) and quantitative (NPS, rating, value-in-range). ## Action types Members pick one or more options from a list. * **Quiz mode** toggle marks correct answer(s); the admin sees accuracy stats (#923). * **CSV upload is no longer available** for multi-choice (removed in #854) — add options manually for full translation control. * Add a **banner image** above the question (#647). Members pick exactly one option. Same quiz-mode support as multiple-choice. Members enter a code you share out of band (e.g. in-store sticker). * **Single-use** — each code can be redeemed once per member. * **Multi-use** — shared code, unlimited redemptions per member. Toggle between modes when you create or edit the action (#1012). Open-ended answer. Reported back as raw submissions in analytics. Open-ended stats were fixed in #730 so partial responses no longer distort counts. Scale (1-5 stars or 0-10 NPS). Reporting shows average, distribution, and NPS score. Fixes shipped in #913 for accurate aggregate numbers. Slider between a min and max. Useful for "how satisfied" or "how likely to recommend" on a custom scale (#616). Dropdown-style selector with a big option list. * Accepts **larger uploads** (#1075) for long lookup tables (city, SKU, etc.). * **"Add option" button appears at 6+ options** so the form doesn't crowd out the rest of the wizard (#1075, #2042). *** ## Rich text descriptions Every action's description supports **rich text** (#672): headings, bold, italics, bullet lists, inline links, and inline images. Use it for context/instructions above the actual question. ## Banner uploads Any action can show a **banner image** above the question (#647). Great for showing product context, a diagram, or a meme. Question action configuration with the banner image upload expanded *** ## CSV import & export For high-volume question content (e.g. trivia banks, product lists), use **CSV import/export**: * **Import** (#761) — bulk-create questions with options and answer keys from a spreadsheet. * **Export** (#756) — download your existing questions as CSV to audit or translate. CSV upload is specifically **not** available for multi-choice question options anymore (#854) — if you need many options managed in bulk, use the **select-from-list** action instead. *** ## Reporting Every question action shows: * Attempts * Correct vs incorrect (quiz mode) * Distribution of answers * Average / NPS score (rating, NPS, select-value-in-range) * Raw submissions (free text) See [Challenge Analytics](/admin-guide/challenges/analytics) for the full analytics surface (completion funnel, per-action stats, participant CSV export). *** ## Examples Three actions: multiple-choice (how satisfied), rating (overall experience), free text (anything to add). Use the action descriptions for context. Five multiple-choice actions with **quiz mode on** and a shared banner image per question. Reward given on high completion accuracy. Ten secret-code actions with single-use codes hidden around a physical venue. Members enter each code to unlock the next hint. # Rich Media Actions Source: https://docs.nudj.cx/admin-guide/challenges/rich-media-actions Image uploads, video actions, object detection, and aspect-ratio cropping Rich-media actions accept file uploads (images, video) or embed video players. They're used for user-generated content (UGC) challenges, moderation-gated submissions, and video-driven experiences. ## Action types ### Image Upload Members upload a photo to complete the action. Admin reviews in the [Review Achievement Flow](/admin-guide/achievements/reviewing-submissions) queue. * **Compression to WebP** happens on upload (#778) — storage + bandwidth optimisation, transparent to admins. * **Strict aspect ratio** enforcement (#828): choose a fixed ratio so all submissions fit the member gallery layout. Cropping is **rounded** (#811) so images look intentional, not cut. * **Banner upload** above the action (#647) for context. * Image upload reporting (#1504) tracks success/failure rates per action. * **Re-upload** after failure works cleanly (#1853) — members can retry without losing their form state. The legacy "image disclaimer" modal was removed (#740). If you need member consent, use the community rules / agreement checkbox pattern instead. ### Video Action Embed a video into the action and require a completion signal (watched to end, specific timestamp hit). See [Video Challenges](/admin-guide/challenges/video-challenges) for the purpose-built admin UI (#1138, #944, #1076, #1114). ### Object Detection Members upload a photo and the action passes only if the AI detects a specific object class (e.g. "dog", "product barcode"). Backwards compatibility for existing object-detection configs was added in #846 — your old detection rules keep working after the refactor. The Action Library modal listing the action types you can add to a challenge *** ## Aspect ratio picker When you create an image-upload action, pick a fixed ratio: | Ratio | Use case | | - | - | | 1:1 square | Profile-style submissions, social amplification | | 10:4 banner | Community banner submissions | | 16:9 landscape | Video stills, widescreen content | | 4:5 portrait | Instagram-style mobile feed | Strict ratio enforcement means members see a crop UI before submission, not after — no surprise distortion in the gallery. *** ## Image reporting The admin view for each rich-media action shows: * Submissions count (success vs. failed upload) * Review queue size (if moderation is on) * CSV export of raw submissions * Per-member thumbnail gallery See [Challenge Analytics](/admin-guide/challenges/analytics) for full reporting context. *** ## Examples Single action: image upload (1:1 square, moderation-on). Members share pictures of their outfit of the day for a reward entry. Video-watch action (full video completion) + a multi-choice question at the end. Object-detection action scoped to "product barcode" — members scan the product in-store to verify purchase. # Social Media Actions Source: https://docs.nudj.cx/admin-guide/challenges/social-media-actions Actions for sharing, following, and YouTube engagement Social-media actions connect your challenge to external platforms. They're proof-of-engagement actions — members do something elsewhere and come back to claim the reward. ## Action types ### Share A "share this link" action. Member clicks, Nudj marks the action complete after the redirect. No cross-platform verification. ### Follow A "follow our account" CTA (Twitter/X, Instagram, TikTok, etc.). Nudj doesn't verify — this is a trust-based action. ### YouTube Video Watch Embed a specific YouTube video; the action completes when the member watches (with the platform player's completion signal). See [Video Challenges](/admin-guide/challenges/video-challenges) for the full video-driven challenge flow. ### YouTube Subscribe Enhanced **YouTube channel input** (#1180) lets you paste any YouTube URL — channel, video, or shorts — and Nudj resolves the canonical channel ID. The action prompts members to subscribe. YouTube Subscribe action with the channel name input field *** ## Creating in the creator wizard The Nudj Agent can draft a YouTube action directly (#1085). Ask it *"add a YouTube subscribe action for our channel"* and it resolves the channel, fills metadata, and drops the action into the current challenge draft. *** ## Video-driven challenges Actions can be layered on top of a single video — timestamp-based questions, mid-video polls, completion checkpoints. The admin UI was purpose-built for this pattern (#1138, #1114, #944). See [Video Challenges](/admin-guide/challenges/video-challenges) for the walkthrough. *** ## Examples One challenge with three actions: (1) watch the launch video, (2) subscribe to the channel, (3) share the channel link. Reward = 150 points + limited-edition badge. Five challenges, each with a video-watch action + a quiz question at the end. Uses [Complete Achievement](/admin-guide/achievements/achievements-as-actions) to gate the next challenge behind finishing the previous. # Key-Gated Challenges Source: https://docs.nudj.cx/admin-guide/challenges/unlock-keys Configure unlock-price (keys) to gate challenges behind an access token or accumulated currency Key-gated challenges require a member to "spend a key" before starting. It's a simple access-control primitive that unlocks premium, member-only, or VIP-tier content. ## How it works Each challenge has an **`unlockPrice`** field — the number of keys a member must spend to open it. Zero = open to anyone. Non-zero = requires keys. When a member opens a gated challenge: 1. Nudj checks the member's key balance. 2. If sufficient, the member is prompted to spend X keys. 3. On confirmation, `unlockPrice` keys are deducted, and the challenge opens. 4. Once unlocked, the member retains access — no re-paying unless the admin resets it. *** ## Configuring the unlock `unlockPrice` is a challenge data field. There is currently **no control for it in the admin challenge editor** — set it programmatically via the [Admin API](#api) or a data import. New challenges default to `unlockPrice: 0` (no gate). Keys are a Nudj currency — members earn them from specific challenges, rewards, or admin gifts, so plan where the supply comes from. Members with fewer than `unlockPrice` keys see the gate; others see the unlock prompt. *** ## Key economy design Balancing keys is an economy decision: Treat keys as a **scarce resource**. If every challenge earns 2 keys and every unlock costs 1 key, gating means nothing. Target: members need 3-5 normal completions to unlock one gated challenge. Use **achievements** to drop keys as milestone rewards — e.g. "complete 10 challenges -> earn 5 keys". Keeps gating aspirational. *** ## API Programmatic unlocks via `POST /challenges/{id}/unlock` — useful for integrations where keys are earned through external events (e.g. Shopify purchases). See [Admin API reference](/api-reference/admin). *** ## When to use gating Gate high-value challenges behind keys so members earn the right to play. Pair with [achievements as actions](/admin-guide/achievements/achievements-as-actions) to build layered progressions. Gate expensive-to-process challenges (large file uploads, AI moderation) behind small unlock prices to reduce spam. Don't gate your **onboarding challenge**. New members need a zero-friction first interaction. # Video Challenges Source: https://docs.nudj.cx/admin-guide/challenges/video-challenges Timestamp-based video actions, YouTube integration, completion UI, admin video builder Video challenges are a first-class pattern in Nudj: members watch a video and answer questions at specific timestamps, or simply complete-to-watch. ## The purpose-built admin UI Nudj has dedicated admin tooling for video challenges (#1138). Once a challenge has video enabled, its **Actions** step gains video-specific controls: * Timestamp picker synced with video playback. * Per-timestamp action creation (question, completion marker, poll). * Video preview inline with the editor. *** ## Creation flow In **Chat**, use the **YouTube** option and paste a video URL — the Nudj Agent creates a challenge with video enabled from it. (The "Video challenge" button on the create screen opens a blank form only; the video tooling is unlocked by enabling video, which the agent does for you.) Enter a YouTube URL. The agent can draft a YouTube action for you (#1085) — it resolves the channel, video metadata, thumbnail. The video config (#1114) sets what counts as "completed": * Full watch * X% watched (e.g. 90%) * Specific timestamp hit Pause at any point in the video and add an action: question, poll, checkpoint. Members see the action mid-video (#944, #949). Distribution works like any other challenge — points, XP, assets, entries. The [preview panel](/admin-guide/challenges/preview-mode) shows the video player experience. Save, flip Live. (Save bug #1286 fixed.) *** ## Action components The video wizard uses dedicated action components (#1076) so the editor works smoothly — each timestamp action has its own UI tuned for video context. *** ## Completion UI The member-facing completion UI (#1089) shows progress against the video: * Progress bar synced with playback * Timestamp pins marked as you pass them * Instant action popups at configured timestamps * Completion celebration at the end *** ## YouTube MCP integration When the Nudj Agent drafts a video challenge, it can use YouTube MCP tools (see [Nudj Agent](/admin-guide/ai-agent)) to fetch metadata, thumbnails, and video lists automatically. *** ## Examples 10-minute tutorial with a quiz question every 2 minutes. Members who get 80% correct earn a completion reward. Recording of a livestream — members watch back within 7 days for bonus points. No mid-video actions, just a completion checkpoint at the end. 2-minute launch reel with a single action at the end: "share this on Twitter". Paired with a subscribe-to-channel action for amplification. # Chat Setup Guide Source: https://docs.nudj.cx/admin-guide/chat-setup-guide Open the Setup Guide panel inside the AI chat and let the agent walk you through every onboarding task, one at a time The **Setup Guide** lives right inside the AI chat workspace. Instead of leaving chat to work through a checklist, you open the guide as a panel, press **Start** on any task, and the [Nudj Agent](/admin-guide/ai-agent) walks you through it step by step — checking what your community already has, pointing you at the relevant docs, and asking one question at a time. It is the same onboarding checklist as the full [Launch Guide](/admin-guide/community-setup), surfaced where you actually do the work. ## Opening the guide There are two ways in, and both land on the same panel: The chat session summary shows a **Setup Guide** row with a progress ring and your completion percentage. Click it to open the guide panel. Open the workspace panel beside the chat and switch to the **Guide** tab (next to Preview, Inventory, and Brand). The guide only appears for communities where onboarding is enabled. If it isn't available for the selected community, the panel shows an empty state instead. Switch community from the top-nav dropdown to see a different community's guide. ## What Start does Each task row shows its state at a glance: | State | What you see | | - | - | | **Incomplete** | A **Start** button | | **Optional** | An "Optional" label alongside Start | | **Locked** | A lock icon — a prerequisite task must be completed first | | **Done** | A check icon and no Start button (completed automatically, marked complete, or skipped) | Pressing **Start** drops a short message into the chat composer — "Help me complete the setup task …" — and quietly attaches the task's context to your next send, so you never have to read or edit a wall of instructions. When you send it, the agent: It calls the `community_guide` tool to read where your community stands on that task and pull the matching guidance. It tells you what the task is and why it matters for your program. It links the relevant page in this documentation so you can go deeper. It guides you to completion step by step, asking one question at a time rather than dumping a form on you. Once it's satisfied the task's criteria are met, it marks the task complete for you — the panel, sidebar guide card, and nav dots update on the spot. You don't have to press Start to get help. Ask the agent directly — "help me set up referrals" or "how do I configure loyalty tiers?" — and it pulls the same task guidance. ## Progress and completion The panel header shows your overall completion percentage, and each section groups its tasks together. Most tasks complete **automatically** once the underlying thing exists — create a challenge and "Create earning mechanics" ticks itself. A handful of tasks (the *Work with AI*, *Launch*, and *Measure* rows) have no automatic signal, so they're marked complete manually — either by you on the full guide page, or by the agent at the end of a walkthrough once you've confirmed the work is done. A few tasks are **advisory** — they are informational nudges that stay out of your headline completion percentage. You can still self-confirm them. ## The task catalogue The guide is organised into eight sections. Here is everything it covers. | Task | What it does | | - | - | | Create your community profile | Set your community name and description so members know what they're joining. | | Save website and social accounts | Record your website and social links — the agent uses them for research and members use them to find you. | | Research Brand Memory | Build your [Brand Memory](/admin-guide/ai-agent) so the agent writes in your voice. | | Task | What it does | | - | - | | Upload logo and brand assets | Add your logo and brand imagery. | | Configure and install the loyalty widget | Set up and embed the [loyalty widget](/admin-guide/settings/embed-and-widget) on your storefront. | | Configure the loyalty hub | Tune the standalone loyalty hub experience. | | Task | What it does | | - | - | | Ask AI to audit your program | Have the agent review your current setup and suggest improvements. | | Ask AI to create a challenge | Draft a [challenge](/admin-guide/challenges/overview) conversationally. | | Ask AI to set up a reward | Draft a [reward](/admin-guide/rewards/introduction) conversationally. | | Ask AI for an engagement report | Get a readout of how your community is doing. | | Ask AI how to boost sales | Get revenue and engagement ideas tailored to your program. | | Task | What it does | | - | - | | Create earning mechanics | Add your first challenge or turn on points for spend — the core of how members earn. | | Publish your first post | *(Advisory)* Share an update in the feed. | | Set up quick actions | *(Advisory)* Add lightweight one-tap ways to earn. | | Tune your points economy | *(Advisory)* Review your points values so earning feels balanced. | | Task | What it does | | - | - | | Add your first rewards | Give members something to spend points on. *(Requires an earning mechanic first.)* | | Set up birthday rewards | *(Optional)* Auto-distribute a reward on each member's birthday. | | Set up referrals | *(Optional)* Reward members for bringing friends. | | Task | What it does | | - | - | | Add achievements | Milestone-based recognition. *(Requires an earning mechanic first.)* | | Configure loyalty tiers | *(Optional)* Tiered status with entry rewards. | | Turn on leaderboards | *(Optional)* Competitive ranking. | | Task | What it does | | - | - | | Preview the member experience | See what members will see before you go live. | | Publish your community | Flip the community live so members can join. | | Task | What it does | | - | - | | Review analytics | Check your [analytics dashboard](/admin-guide/analytics/admin-dashboard) to see how the program performs. | ## Full guide anytime The panel has an **Open full guide** link in its footer that takes you to the complete Launch Guide, where you can tick tasks manually, review prerequisites, and see the whole checklist on one page. ## Related Everything the AI co-pilot can do inside the chat workspace. The end-to-end onboarding walkthrough. # Creating a Community Source: https://docs.nudj.cx/admin-guide/community-setup Build, configure, and go-live with a branded community A **community** is the branded space members actually experience — logo, colours, rules, feed, challenges, and rewards. Orgs can have many communities; each one has its own theme and content. ## Creation flow Name, URL slug, short description, and a banner image. Communities start in **Draft** by default — invisible to members until you flip them. Use the **status dropdown** on the community settings page to switch between **Draft**, **Live**, **Archived**, and **Disabled**. When you flip Draft to Live, Nudj shows a **confirmation popup** so you cant publish by accident. Read it. Confirm. Mark any community as **Featured**. Featured communities are surfaced first in multi-community experiences (embed launcher, landing page, org-level dashboard). Under **Legal & Policies**, add **community rules** using the rich-text editor. Choose whether members must **agree to rules via a checkbox** on join, and whether the rules panel is a permanent, always-visible toggle. See [Legal & Policies](/admin-guide/settings/legal-policies). Each community can override the org theme. The **theme editor** has a **live preview iframe** on the right so you can see your changes in real-time. See [Community-Level Theming](/admin-guide/settings/community-branding). *** ## Community statuses | Status | Visible to members? | In admin lists? | | - | - | - | | **Draft** | No | Yes | | **Live** | Yes | Yes | | **Archived** | No | Only on the [Archived page](/admin-guide/getting-started/archived-communities) | | **Disabled** | No | Yes (greyed out) | **Archiving** preserves all data so you can restore later. **Disabling** is a soft kill for temporary take-downs (e.g. during an incident). *** ## Rules and agreement Communities can require members to accept rules before participating. Use the TipTap rich-text editor (headings, lists, links, inline images) to write your rules. Saved to the community's `legalConfig`. Require members to tick an **"I agree to the rules"** checkbox before joining. Useful for contests/sweepstakes compliance. Force the rules panel to stay visible (e.g. always-on footer). Once set, admins cannot silently disable the rules — a safeguard for regulated verticals. *** ## Going live — checklist Status dropdown set to **Live** (confirmation popup accepted). Community has a name, banner, and short description. At least one challenge is **Live** (or scheduled). Theme has been previewed in the live iframe. Rules, agreement, and landing page are configured if applicable. *** ## Archiving & restoring To archive: **Community Settings -> Status -> Archived**. The community disappears from member apps but stays on the [Archived Communities](/admin-guide/getting-started/archived-communities) page. Restore by switching status back to **Live** or **Draft**. Archive seasonal or one-off communities (holiday pop-ups, product launch) rather than deleting — you keep all member data and analytics for future comparison. # Creating an Organisation Source: https://docs.nudj.cx/admin-guide/creating-organization Create your top-level organisation and land in the community creation flow Every Nudj customer starts at the **organisation** level. Organisations own one or more **communities**, team members, RBAC roles, branding, domains, and integrations. See [Organisations vs Communities](/admin-guide/organizations-vs-communities) for the mental model. ## Single-step creation Nudj consolidated organisation creation into one form. You pick a name, slug, and optional logo, and save — no multi-screen wizard. Go to **[admin.nudj.cx](https://admin.nudj.cx)**. Authenticate with Google, Azure, or email magic-link. Enter your **organisation name** (shown in the top nav), a **URL slug** (used in `/{orgSlug}` routes), and optionally upload a **logo**. The logo appears in the admin top nav and on member-facing landing pages. After saving, Nudj redirects you directly to the **Create Community** screen. Every org needs at least one community to do anything useful. See [Community Setup](/admin-guide/community-setup). If you already belong to an org (e.g. a teammate invited you), you'll be routed straight to that org's dashboard — no creation step. ## What you configure next Once the org exists, head to **Organisation Settings** to tune the rest: Org name, logo, landing-page header, timezone. Invite collaborators and assign Viewer / Moderator / Manager / Admin / SuperAdmin. Attach custom member-facing domains. Configure standard and enterprise SSO providers. ## The org switcher If your account belongs to multiple orgs, the **top-nav org switcher** lets you jump between them. Each org has its own isolated cookie, roles, and content — switching does not leak data between orgs. When you switch org, Nudj lands you on that org's **dashboard** (`/{orgSlug}`) — not on a specific community. Pick a community from the community dropdown to drill in. ## Multi-community orgs You can run many communities under one org (e.g. a parent brand with regional sub-communities, or a SaaS customer with one community per enterprise tenant). Team members with org-level roles see every community; community-scoped roles only see the communities they're assigned to. See [Roles & Permissions](/admin-guide/settings/rbac-and-roles). Start with one community. Spin up more only when you have distinct audiences or programs — not as a substitute for categories. # Dashboard Overview Source: https://docs.nudj.cx/admin-guide/dashboard Your community command centre: live stats, activity, and quick actions There are two dashboards in the Control Room: | Dashboard | URL pattern | Purpose | | - | - | - | | **Org dashboard** | `/{orgSlug}` | Aggregate stats + community grid + org switcher | | **Community dashboard** | `/admin` (after picking a community) | Live activity, challenge/reward/post summaries, analytics tiles | Both share a consistent layout so switching between them feels seamless. *** ## Org dashboard Landing here when you switch organisations. You see: * **Aggregate stats** across every community (members, active, completions, redemptions) * A **community grid** showing each community with status, member count, and recent activity * Org-switcher and community-picker in the top nav * **Org logo** in the top-left (uploaded in General Settings) Click any community card to drill into its dashboard and admin. That sets a community cookie and redirects to `/admin`. *** ## Community dashboard The **community-level admin home** is redesigned around four tiles: Active members, new joiners, DAU/WAU, and completion trend. Real-time feed of joins, challenge completions, rewards claimed, achievements unlocked. Counts of live challenges, rewards, achievements, and posts. Total members, verified, anonymous, and role breakdown — linked to the Users page. The live activity feed powers directly from API events (see [Analytics](/admin-guide/analytics/interpreting-data)). The activity chart uses the theme's **primary colour** so it visually matches your brand. *** ## Light mode and AI button The admin shell supports both **light** and **dark** modes — toggle via the top-right. The **AI chat button** sits in the top nav and opens the Nudj Agent (see [Nudj Agent](/admin-guide/ai-agent)). *** ## Drill-down sidebar The redesigned left sidebar uses a **drill-down pattern**: top-level sections (Engagement, Gamification, Users, Settings) expand inline without leaving the page. Active community context shows in the top nav, not the sidebar — the sidebar is purely navigational. *** ## Quick actions Every dashboard has a top-right **+ New** button — same dropdown you see everywhere else in the admin. One click to start a new challenge, reward, post, or achievement. See [Navigation Overview](/admin-guide/navigation-overview) for the full shell tour, and [Dashboard Reference](/admin-guide/analytics/admin-dashboard) for a field-by-field breakdown of every card. # What Gets People Participating Source: https://docs.nudj.cx/admin-guide/engagement-overview The four pillars of community engagement—challenges, rewards, achievements, and posts Your community thrives on four engagement pillars—each designed to turn passive browsers into active participants. Think of them as the building blocks of habit formation, but less boring than that sounds. Sure, the platform does plenty of other things (analytics, integrations, user management, the works), but these four features are where engagement actually happens. Master these and the rest is just configuration. ## The Four Engagement Areas Nudj gives you four distinct ways to capture attention, reward action, and build momentum: **Challenges** are your primary engagement engine. They're the "do this, get that" experiences that drive daily activity—everything from quick quizzes to multi-step quests. When engagement dips (and it will), challenges are your first line of defense. **Rewards** are the payoff. Points unlock them, users claim them, and suddenly your community has a reason to come back tomorrow. Done right, rewards feel earned instead of algorithmic. Done wrong, they're digital clutter nobody wants. **Achievements** recognize the milestones your members actually care about—first challenge completed, ten-day streak maintained, top-tier status unlocked. They're the silent motivators working in the background while challenges grab the spotlight. **Posts** keep the conversation flowing between structured activities. They're your community's heartbeat—announcements, updates, member stories, and the kind of content that makes people check in even when there's no challenge waiting. ## How They Work Together These aren't isolated features—they're an interconnected system. Launch a challenge, attach rewards to completion, trigger achievement unlocks for milestones, then post about it to drive visibility. That's the engagement flywheel in action. Most successful communities start with challenges (the hook), layer in rewards (the motivation), sprinkle achievements throughout (the status system), and use posts to maintain momentum between campaigns. You don't need to master all four on day one, but understanding how they connect matters from the start. Create the experiences members actually complete—from quick wins to epic quests Design incentives that motivate without feeling like digital bribes Recognize milestones and build progression systems that keep members engaged Maintain community momentum with updates, stories, and conversations # Content Best Practices Source: https://docs.nudj.cx/admin-guide/engagement/content-best-practices How to write challenges and rewards that your community will actually love In a community, your words are your "User Experience." A well-written challenge can feel like a game; a poorly written one feels like homework. This guide helps you strike the right tone and structure your content for maximum engagement. **Time to Read**: 6 minutes **Goal**: Increase challenge completion rates by improving your copy and imagery. ## 1. The Power of the Hook (Titles) Your title is the only thing a user sees before they decide to click. | Instead of... (Boring) | Try this... (Exciting) | Why it works | | - | - | - | | "Survey about the app" | "Help us build the future 🚀" | Focuses on impact and uses an emoji. | | "Weekly Quiz" | "Can you beat the Pro level? 🧠" | Creates a challenge and uses a question. | | "Refer a friend" | "Give $10, Get $10 💸" | Highlights the immediate benefit. | | "Watch this video" | "Sneak Peek: What's coming next" | Creates curiosity and "exclusive" feel. | ## 2. The "Hand-Holding" Description Once they click, don't overwhelm them. Use the **3-S Rule**: * **Short**: Keep descriptions under 200 characters if possible. * **Specific**: Tell them exactly what to do. *"Watch the video and answer the 3 questions below."* * **Social**: Use "We" and "You." *"We'd love to hear your thoughts on this!"* ## 3. Imagery: More Than Just Decoration A custom image gives a challenge a far better chance of being clicked than the default treatment. * **Use faces**: Humans are biologically wired to look at faces. Use photos of your team or community members. * **Avoid generic stock**: A real photo of your office is better than a high-res photo of a generic office. * **Brand colors**: Use your primary brand color in the background of your images for consistency. * **Meme responsibly**: A well-placed meme can drive huge engagement, but use them sparingly so they don't lose their "spark." ### Image sizes Each entity crops to its own aspect ratio, so there is no single correct size: | Image | Size | Ratio | | - | - | - | | **Challenge** | 1440 x 1440px | 1:1 square | | **Reward** | 1440 x 1080px | 4:3 | | **Reward (badge type)** | 512 x 512px | 1:1 square | | **Post** | 1440 x 1440px | 1:1 square | **Challenge images are locked to square.** The challenge cropper does not let you change the aspect ratio, so a 4:3 upload gets cropped to 1:1 whether you want it to or not. Compose challenge art square from the start, and keep anything important away from the edges. ## 4. Structuring Action Steps If a challenge has multiple steps, order them by **increasing difficulty**: 1. **Step 1 (Easy)**: "Click here to see the new feature." (Get them committed). 2. **Step 2 (Medium)**: "What is your favorite part about it?" (Get them thinking). 3. **Step 3 (Harder)**: "Record a 30-second video of you using it." (The big win). Getting someone through an easy first step is the best thing you can do for your completion rate. Once they've started, they're far more likely to see it through. Reorder steps by dragging the handle on each action in the challenge's **Actions** list. The order you set is the order members see. Save the challenge before reordering. On a challenge you haven't saved yet, the list will appear to reorder but the new order isn't persisted. ## 5. The completion screen When a member finishes the last action, Nudj shows a completion screen with their reward. Your community uses one of two versions: * **Immersive** (the default): a multi-step sequence covering stats, points, XP, leaderboard position, rewards, and any badge earned. * **Classic** (opt-in): a single screen instead of the multi-step sequence. **None of the completion copy is per-challenge.** There is no success-message field on a challenge, so you can't write a bespoke celebration for one of them. Everything below is community-wide. What you can actually change: * **Colors**, via **Experience -> Settings -> Common Branding**, then open **Challenge Complete Screen**. This is a community-level setting, so every challenge in the community shares it. Two challenges can't have different completion colors. Until you set a value here the community inherits the organisation default from **Org Settings -> Brand Defaults -> Branding**. These colors only reach the **immersive** version; the classic screen ignores them. * **Wording**, via **Org Settings -> Advanced -> Languages**, then **Edit** on a language and change the `challenges.complete.*` keys. These drive **both** versions. `challenges.complete.title` ("Challenge Complete!") and `subtitle` ("Congratulations on your achievement!") are the immersive screen's opening heading, and `badge_unlocked` ("Badge Unlocked!") heads its badge step. Because the celebration is fixed, put your personality in the parts you do own: the title, the description, and the reward name members see when they land there. ## The Engagement Checklist Before you hit "Live," ask yourself: * [ ] Does the title make me want to click? * [ ] Is the first step so easy they can't say no? * [ ] Is it clear what they get at the end? * [ ] Does the image look good on mobile? *** **Next Step**: Learn about [different Challenge Types](/admin-guide/challenges/action-types-overview). # E-commerce Launch Source: https://docs.nudj.cx/admin-guide/examples/ecommerce-launch Drive product launch engagement with actions, points, and tiered rewards Run a launch campaign that educates customers, drives social buzz, and rewards participation. ## Outcome * Users complete a short sequence of actions (watch, learn, share) * Earn points to redeem instant perks and enter a launch giveaway * You collect structured feedback and amplify reach via social actions ## Step-by-step See [Community Setup](/admin-guide/community-setup) and [Branding & Theming](/admin-guide/settings/branding-theming). Title: "Product X Launch Quest". Status: Draft. Featured: On. Completion: 150 points, 400 XP. Consider a small points per action and a completion bonus. Ensure the discount code shows on completion and the giveaway is discoverable. Set Live. Promote via email, product page banner, and social posts. ## Suggested configuration ```json Completion Reward theme={null} { "rewards": { "completion": { "points": 150, "xp": 400 } } } ``` ```json Discount Asset theme={null} { "allocationsType": "assets-only", "assetPointsPrice": 0, "assetsSupply": "Infinity", "maxAssetsPerUser": 1, "status": "live", "redemptionConfig": { "type": "codes" } } ``` ```json Giveaway Entries theme={null} { "allocationsType": "entries-only", "entryPointsPrice": 25, "entriesSupply": 5000, "maxEntriesPerUser": 10, "drawsAt": "2025-10-01T00:00:00Z", "status": "live" } ``` ## KPIs * CTR from challenge to product page * Discount redemption rate * Giveaway entries per user * Social shares and referral traffic # Live Event Activation Source: https://docs.nudj.cx/admin-guide/examples/live-event Activate attendees with check-ins, content tasks, and post-event follow-ups Engage attendees before, during, and after your event with a simple challenge flow. ## Outcome * Attendees check in, complete session tasks, and share content * Earn points to redeem instant perks or enter a prize draw * You gather session feedback and boost social reach ## Step-by-step Use [Community Setup](/admin-guide/community-setup) and schedule live dates. Title: "Event Quest". Use Scheduled status with startsAt/expiresAt. Completion: 200 points, 500 XP. Create a small instant asset (e.g., drink voucher) and a grand-prize giveaway (entries-only). Set Live at doors open. Print a QR code linking to the challenge. Announce from stage. ## Suggested configuration ```json Scheduled Challenge theme={null} { "status": "scheduled", "startsAt": "2025-11-10T08:00:00Z", "expiresAt": "2025-11-12T23:59:59Z", "rewards": { "completion": { "points": 200, "xp": 500 } } } ``` ## Tips * Keep actions short to minimize queue congestion * Use staff to assist with check-in codes * Post-event: send follow-up challenge with highlight content # YouTuber Challenge Source: https://docs.nudj.cx/admin-guide/examples/youtuber Create a video-driven challenge with timestamp tasks and a giveaway reward Launch a challenge around a YouTube video to grow engagement, gather insights, and drive measurable actions. ## Outcome * Users watch your video, answer timestamp-based questions, and complete social actions * Users earn points that they can spend on giveaway entries * You get richer audience data and leaderboard-driven engagement ## Prerequisites * Nudj admin account and an organisation * A community (create one in the [Community Setup](/admin-guide/community-setup) guide) * A published YouTube video URL ## Step-by-step Follow [Community Setup](/admin-guide/community-setup). Apply your visuals in [Branding & Theming](/admin-guide/settings/branding-theming). Go to Engagement → Challenges, click Create Challenge, give it a title like "Watch & Win: New Video" and set Status to Draft. Add 3–5 actions that validate video engagement and promote your channel: Assign points (spendable) and XP (status) to actions and for challenge completion so users can purchase giveaway entries and climb leaderboards. In Engagement → Rewards, create an Entries Only reward (giveaway). Set an entry points price (e.g., 25 points per entry) and schedule a draw date. In the challenge’s reward configuration, confirm base points/XP and link the giveaway reward if needed for visibility. Set the challenge to Live. Copy the public link from the challenge page.
Add it to your YouTube description and pinned comment, and mention it in your video or community tab post.
## Suggested configuration ```json Challenge (key settings) theme={null} { "status": "live", "isLinear": false, "details": { "title": "Watch & Win: New Video" }, "rewards": { "completion": { "points": 100, "xp": 300 } } } ``` ```json Giveaway Reward (entries-only) theme={null} { "allocationsType": "entries-only", "entryPointsPrice": 25, "entriesSupply": 10000, "maxEntriesPerUser": 20, "drawsAt": "2025-09-30T23:59:59Z", "status": "live" } ``` ## Tips Use timestamp-based questions to validate watch-through without requiring users to leave the video page for long. Leaderboards reflect XP. To emphasize competition, grant small XP per action and a bonus on completion. ## Measure success * Challenge starts vs completions * Giveaway entries purchased (points spent) * New subscribers/likes vs baseline * Leaderboard participation and return visits # Game Configurations (Strategy) Source: https://docs.nudj.cx/admin-guide/gamification/game-configurations Strategy and balancing notes for Nudj game configs — see Games for the visual builder This page covers **strategy and balancing** for Nudj's game system. For the feature-level documentation of the **visual game builder** (creating new games, per-game-type configuration, preview, branding, repeatability, etc.), see [Games](/admin-guide/gamification/games). ## Picking a game | Game type | Good for | Avoid when | | - | - | - | | Flick | Quick-action, high-repeat games (arcade launch) | Members on slow devices — animation-heavy | | Catch | Hand-eye reaction — good for younger audiences | Accessibility-critical communities | | Memory | Knowledge / brand familiarity | Players who dislike pattern-matching | | Basketball | Big-screen / playful communities | Serious / B2B programs | ## Balancing rewards Games convert **score** into points/XP/rewards. Your settings decide how rewarding the game feels: * **High xpPerScore** — makes every action feel rewarding. Good for onboarding. * **Score thresholds** — gate bigger rewards behind harder scores. Good for retention. * **Outcome-based** (scratch products) — make the big reward rare. Good for hype. See the full config options in [Games](/admin-guide/gamification/games). ## Showing vs hiding games Games have two visibility toggles: * **Community scope** (#1851) — whether a given game config is available in this community. * **Show-in-games-tab** (#2104) — whether it appears in the member Games tab. Use scope to run different games per community. Use show-in-games-tab to let admins attach games to challenges (via the [Game action](/admin-guide/challenges/platform-actions#game)) without cluttering the games tab. ## Featured games The **featured toggle** (#1930) pins a game to the top of the games tab. Reserve for launches or seasonal events — overuse kills the signal. ## Repeatability The repeatability toggle (#2304) decides whether the same game can be replayed by a member for rewards. Off = one-and-done. On = members can chase higher scores repeatedly. ## Next steps Full documentation of the visual game builder, per-game-type config, preview, branding, translations. # Games Source: https://docs.nudj.cx/admin-guide/gamification/games Visual game builder for flick, catch, memory, and more — per-game-type config, safe-zone preview, translations Nudj ships a full **visual game builder** (#1812) for bite-sized mini-games embedded in your community. Each game type (flick, catch, memory, basketball, scratch, etc.) has a purpose-built admin UI. ## Supported game types Fast-tapping arcade game. Score = how many objects flicked in time limit. Catch falling objects. Has a **safe-zone preview** (#2168) so admins can see hit boxes. Card-matching memory game. Alt text on images required for accessibility (#2389). Free-throw hoops game. Scratch-card reveal — outcome-based rewards. *** ## Building a game * New -> Game -> pick type. Each type has its own visual builder. Configure colours for backgrounds, UI elements, score overlays (#2232). Flows from the community theme by default. Logo, background, and per-game-type asset uploads (#2136). Covers background images, flick game images (#2157 for removal), memory game card images. Score-to-XP conversion, thresholds, outcome-based triggers. See [Game Configurations (Strategy)](/admin-guide/gamification/game-configurations) for balance tips. Repeatability toggle (#2304) — can members play for rewards more than once? Game pro-tips are translated per language (#2154). Play the game in the preview before publishing. Preview/delete guards (#1179) prevent accidental breakage. *** ## Visibility toggles Each game has three visibility levels: * **Community scope** (#1851) — which communities can access this config. * **Show-in-games-tab toggle** (#2104) — whether to show in the member Games tab, or only via challenge actions. * **Featured toggle** (#1930) — pin to the top of the Games tab. Plus **opensInNewTab** (#2189) for games better experienced in a new browser tab (e.g. larger-canvas games). *** ## Languages Full translation support for game copy — labels, pro-tips, button text. Language-switch bug in game config fixed in #2137. Search within the game config also matches against saved (not just original) values (#2247). *** ## Assets & accessibility Memory game (and other image-based games) require **alt text** on every image (#2390) for screen reader accessibility. Nudj enforces this at save time. *** ## Form UX fixes The builder has received many UX polish rounds: missing-fields handling (#1846), validation messaging (#1852), creation flow (#1902), and incidental fixes (#1932). *** ## Games as challenge actions Games can be the payload of a challenge's [Platform action](/admin-guide/challenges/platform-actions#game). Reward distribution on the action is wired to the game's scoring config. *** ## Best practices **Don't ship a broken game**. Preview end-to-end on real devices — mobile browsers have quirks that don't show in desktop preview. Start with **one game type** per community. Adding 5 games at launch dilutes attention. For outcome-based games (scratch), keep the **big-reward probability low** (1-5%). Too common = no excitement. # Leaderboards Source: https://docs.nudj.cx/admin-guide/gamification/leaderboards Configure competitive rankings with multi-language support, disabled-state visibility, and distribution configs Leaderboards turn accumulated XP (or challenge completions, streaks, custom events) into competitive rankings. Members see where they stand; admins configure what gets ranked and how. ## Creating a leaderboard Top nav -> **+ New -> Leaderboard**. Name, description, icon. Rendering was stabilised in #781 so multi-column layouts don't flicker. Pick the metric that ranks members: * **XP earned** (total or in a window) * **Challenges completed** * **Streak length** * **Custom event count** Rolling window for the ranking: * **All time** — cumulative forever. * **Monthly / weekly / daily** — resets automatically. * **Custom range** — fixed start/end. Attach rewards to top N positions. Distribution configs let you tier — different rewards for #1, #2-5, #6-10 etc. Avoids reward-save duplication bug (#1070). **Bonus XP defaults to 0** (#688) — set it explicitly if you want it to fire. * **Language** — per-locale titles/descriptions (#1062). * **Disabled state** — disabled leaderboards still show in admin lists (#1650) so you can re-enable without hunting. *** ## Language support Leaderboards fully support multi-language (#1062). Edit the title, description, and reward copy per locale. Members see the leaderboard in their chosen language. *** ## Dynamic count The leaderboard row count is **not hardcoded** (#1376) — configure how many positions display (top 10, 25, 50, 100). Members outside the displayed range still accumulate rank, they just don't render in the list. *** ## Name input focus The name input field retains focus properly (#2230) — you can type the full name without re-clicking after each keystroke (this used to be a pain point on longer names). *** ## Disabled leaderboards Turn off a leaderboard without deleting it — the **disabled state** keeps the config safe but hides it from members. Disabled boards still show in the admin list (#1650) so you can flip them back on. *** ## Patterns Metric: total XP, window: monthly. Rewards: tiered (big for #1, medium for #2-5, small for #6-10). Resets on the 1st of each month. Metric: longest streak, window: all-time. Rewards: one big badge for #1, smaller badges for #2-10. Metric: challenges completed, window: weekly. Rewards: reward entries (giveaway tickets) for top 20. Separate leaderboards per [loyalty tier](/admin-guide/rewards/loyalty-tiers) so Bronze members compete with Bronze, Gold with Gold — healthier competition than a single mixed board. *** ## Sidebar fix note Leaderboard studio sidebar was previously broken (#2239). If you're on a very old admin build and see rendering issues, force-refresh or clear the Turbopack cache. *** ## API * `GET /admin/leaderboards` — list * `POST /admin/leaderboards` — create * `PATCH /admin/leaderboards/:id` — update * `GET /admin/leaderboards/:id/distribution-events` — distribution configs See the [Admin API reference](/api-reference/admin). # Gamification System Overview Source: https://docs.nudj.cx/admin-guide/gamification/overview How to use gamification mechanics to drive engagement and build community Think of your community like an ant colony. Everyone's working toward the goal, but what keeps them going? Recognition, progress, and a sense they're part of something bigger. Nudj's gamification system is how you build that feeling. Gamification transforms routine engagement into a rewarding journey. Users earn currency, compete in friendly ways, unlock achievements, and build streaks of participation. It's not about tricks—it's about making effort visible and progress satisfying. As Confucius said, "It does not matter how slowly you go as long as you do not stop"—consistent participation beats sporadic effort every time. ## The Four Core Features Points (spendable currency) and XP (permanent progression). Configure earning rates, spending values, and level systems. Let users see where they stand. Create global, time-based, or category-specific rankings that keep engagement fresh. Reward showing up regularly. Configure streaks, unlock milestones, and set up progression pathways that celebrate growth. Customize your community's gamification. Set point values, achievement thresholds, and design seasonal competitions. ## Additional Features Turn your engaged users into community ambassadors. Reward successful referrals and viral growth. Where points become real prizes. Set up rewards, manage inventory, and let users spend their earnings. ## Quick Start You don't need to set everything up at once. Start with: 1. **Points & XP** — Set basic earning rates and what XP unlocks 2. **One leaderboard** — Get competition going with a simple all-time ranking 3. **Add from there** — Streaks, achievements, seasonal events as your community grows The best gamification systems feel invisible. Users don't think about mechanics—they just know their effort matters, they can see their progress, and there's always something to work toward next. # Referral Programs Source: https://docs.nudj.cx/admin-guide/gamification/referral-programs Community-scoped referral reward distribution: referrer + referee rewards, backing achievement Nudj has a first-class **referral system** where each community has its own `referralConfig`. When Member A's referral link brings in Member B, both earn rewards based on that config — all scoped to the community. ## How it works Each community has a `referralConfig` with two distribution events — one for the **referrer** reward, one for the **referee** reward. Rewards are allocated from a backing **Referral achievement**. Nudj generates a community-scoped referral link tied to Member A. When Member B clicks and joins, they're linked to Member A as their referrer. Both Member A (referrer) and Member B (referee) earn the configured rewards. Achievements and analytics get updated. *** ## Referral achievements are isolated Referral achievements are tagged with a special AchievementType and **filtered out of standard achievement APIs** so they don't appear in normal "list achievements" results. They're treated as infrastructure-level objects, managed only through the referral config. *** ## Configuring the program Go to **Gamification -> Referrals** to set: * Referrer reward (points/XP/asset/entry) * Referee reward (same options) * Active / disabled toggle * Community-scope — each community's referrals are separate *** ## Status The referral admin page was reverted in #2111 while underlying mechanics were stabilised — check your admin panel to confirm the current flow matches this document before promoting referrals externally. *** ## Further reading See `docs/analysis/referral-reward-distribution-analysis.md` in the repo for the internal data-model deep-dive on how referral events propagate through the achievement system. # Streaks Source: https://docs.nudj.cx/admin-guide/gamification/streaks-mastery Check-in streaks and save-fix patterns Streaks reward consecutive behaviour — typically daily or weekly check-ins. Members see a "flame" indicator for their current streak length and milestones unlock rewards at configured intervals. ## Streak sources Streaks are driven by specific action types: | Action | Streak metric | | - | - | | Check-in action | Consecutive calendar days with at least one check-in | | Challenge completion | Consecutive days with at least one challenge completed | | Custom event | Any event you fire via the Integration API | Enable the streak option on a **check-in action** (#1214) during challenge creation to have that action contribute to the member's streak counter. ## Streak save behaviour Once a streak is set, Nudj maintains it atomically (#932 fixed a race condition where rapid actions could silently break the streak). Streaks end when: * The member misses a day (daily streaks) or a week (weekly streaks). * An admin manually resets the member's streak. ## Milestone rewards Pair streaks with [achievements](/admin-guide/achievements) that fire on `streak_milestone` events at configurable lengths (7, 14, 30, 100 days). Rewards can stack — bigger rewards for longer streaks. ## Tips Short daily streaks (7 or 14 days) feel achievable to newcomers. Gate longer streaks behind the short ones via tiered achievements. Don't build your whole gamification around streaks. Miss-forgiveness is important — consider "streak shields" (a reward that absorbs one missed day) for power users. # Streaks & Progression Source: https://docs.nudj.cx/admin-guide/gamification/streaks-progression Building habits that stick without creating user burnout or anxiety Streaks can be incredibly powerful for building engagement habits—or incredibly frustrating when life gets in the way. The key is designing systems that encourage consistency without punishing reality. ## Streak Psychology **Why Streaks Work** * **Habit Formation**: Daily actions become automatic behaviors * **Loss Aversion**: People hate breaking winning streaks more than they love starting them * **Visual Progress**: Seeing consecutive days provides immediate satisfaction * **Social Proof**: Sharing streak achievements creates community pressure and support **Why Streaks Fail** * **All-or-Nothing Pressure**: Miss one day, lose everything * **Life Interference**: Vacations, illness, and emergencies kill motivation * **Grinding Fatigue**: Maintaining streaks becomes a chore rather than enjoyment * **Comparison Stress**: Seeing others with longer streaks creates discouragement ## Smart Streak Design **Grace Period Systems** Don't let one missed day destroy weeks of progress: * **24-Hour Buffer**: Late login still counts for "yesterday" * **Weekend Flexibility**: Reduced requirements on weekends * **Streak Freeze**: Earn protection days that prevent streak loss * **Recovery Window**: 48 hours to "make up" a missed day **Multiple Streak Types** Different activities, different requirements: **Login Streaks** * **Simple Access**: Just opening the app counts * **Minimal Commitment**: Lowest barrier to maintenance * **Gateway Behavior**: Leads to deeper engagement * **Universal Accessibility**: Everyone can participate regardless of skill **Activity Streaks** * **Challenge Completion**: Actually doing something meaningful * **Quality Threshold**: Minimum effort required to count * **Skill Building**: Encourages regular practice and improvement * **Progress Oriented**: Focused on advancement rather than just showing up **Social Streaks** * **Community Interaction**: Comments, posts, helping others * **Relationship Building**: Encourages ongoing community participation * **Mutual Benefit**: Helps others while maintaining personal streaks * **Social Accountability**: Community members notice and encourage consistency Be very careful with activity streaks. Requiring daily challenge completion can quickly turn from motivating to overwhelming. Consider weekly goals instead of daily requirements. ## Progression Pathways **Level-Based Advancement** Clear milestones that show meaningful progress: **Beginner Journey (Levels 1-5)** * **Focus**: Learning platform basics and building initial habits * **Requirements**: Low-pressure introduction to key features * **Rewards**: Immediate positive feedback and encouragement * **Timeline**: 2-4 weeks of casual engagement **Skill Development (Levels 6-15)** * **Focus**: Deepening engagement with platform capabilities * **Requirements**: More diverse activity participation * **Rewards**: Feature unlocks and increased earning potential * **Timeline**: 2-6 months of regular engagement **Community Leadership (Levels 16+)** * **Focus**: Contributing to community growth and mentorship * **Requirements**: Helping others and creating valuable content * **Rewards**: Recognition, exclusive access, platform influence * **Timeline**: 6+ months of sustained, high-quality engagement **Unlock Mechanisms** How users gain access to new features and opportunities: * **XP Thresholds**: Accumulate experience points through various activities * **Achievement Gates**: Complete specific accomplishments to unlock features * **Time-Based Access**: Some features unlock after sustained engagement * **Community Contribution**: Help others to unlock advanced capabilities ## Streak Rewards and Recognition **Milestone Celebrations** Make reaching streak goals feel genuinely rewarding: **Weekly Milestones (7, 14, 21 days)** * **Modest Recognition**: Badge or notification * **Small Bonus**: Extra points or minor reward * **Social Sharing**: Easy way to announce the accomplishment * **Encouragement Message**: Personalized congratulations **Monthly Achievements (30, 60, 90 days)** * **Significant Recognition**: Special badge or profile indicator * **Meaningful Reward**: Valuable item or exclusive access * **Community Celebration**: Public acknowledgment of dedication * **Bonus Multipliers**: Enhanced earning rates for continued engagement **Epic Streaks (100+ days)** * **Elite Status**: Permanent profile indicators * **Exclusive Access**: Special features or community areas * **Legacy Recognition**: Hall of fame or similar permanent recognition * **Platform Influence**: Input on feature development or community direction **Multiplier Benefits** Longer streaks should enhance the platform experience: * **Points Multipliers**: 1.5x points after 7 days, 2x after 30 days * **XP Bonuses**: Enhanced experience point earning for activities * **Priority Support**: Faster response times and premium assistance * **Beta Access**: Early access to new features and updates ## Managing Streak Anxiety **Healthy Streak Culture** Prevent streaks from becoming sources of stress: **Communication Strategies** * **Life Happens Messaging**: Normalize that breaks occur * **Recovery Encouragement**: Focus on restarting rather than dwelling on breaks * **Multiple Success Paths**: Highlight achievements beyond just streaks * **Community Support**: Encourage users to cheer for each other's efforts **Flexible Requirements** * **Difficulty Scaling**: Easier requirements during busy periods (holidays, exams) * **Personal Customization**: Let users set their own streak goals * **Alternative Activities**: Multiple ways to maintain streaks * **Break Prevention**: Warning systems when users are close to losing streaks **Stress Reduction Features** * **Streak Insurance**: Use points to buy protection days * **Team Streaks**: Group accountability that doesn't depend on individual perfection * **Seasonal Adjustments**: Modified requirements during known difficult periods * **Opt-Out Options**: Easy way to pause streak tracking when needed ## Advanced Progression Features **Personalized Pathways** Not everyone should follow the exact same progression route: **Interest-Based Tracks** * **Content Creator Path**: Focus on social features and community building * **Challenger Path**: Emphasize problem-solving and skill-based activities * **Social Connector Path**: Prioritize community interaction and relationship building * **Explorer Path**: Reward trying new features and discovering platform capabilities **Adaptive Difficulty** * **Performance Monitoring**: Track user success rates and adjust challenges accordingly * **Comfort Zone Expansion**: Gradually introduce more challenging requirements * **Skill Recognition**: Accelerate progression for users demonstrating mastery * **Support Intervention**: Additional help for users struggling with progression **Collaborative Progress** * **Buddy Systems**: Pair users for mutual streak accountability * **Team Challenges**: Group achievements that support individual progress * **Mentorship Programs**: Experienced users help newcomers navigate progression * **Community Goals**: Collective achievements that benefit everyone's progression ## Analytics and Optimization **Streak Health Metrics** Monitor how well your streak systems are working: **Participation Rates** * **Initial Adoption**: How many new users start streak tracking * **Sustained Participation**: Retention rates for streak maintenance * **Recovery Patterns**: How many users restart after breaking streaks * **Long-term Engagement**: Correlation between streaks and overall platform usage **Break Analysis** * **Common Break Points**: When users typically lose streaks * **Recovery Rates**: Percentage who restart vs. abandon streak tracking * **Seasonal Patterns**: How external factors affect streak maintenance * **User Feedback**: Direct reports on why streaks were abandoned **Progression Effectiveness** * **Level Distribution**: How users spread across progression levels * **Advancement Rates**: Time taken to reach various milestones * **Feature Utilization**: Whether unlocked features drive continued engagement * **Satisfaction Correlation**: How progression affects overall user satisfaction **Optimization Strategies** * **A/B Testing**: Compare different streak requirements and reward structures * **Behavioral Analysis**: Identify patterns that lead to sustained engagement * **User Journey Mapping**: Understand complete progression experience * **Intervention Timing**: When to offer help or adjust requirements The best streaks feel like natural habits rather than imposed requirements. If users are stressing about maintaining streaks, the requirements might be too rigid. Track your streak abandonment rates carefully. If more than 30% of users who start streaks abandon them within the first two weeks, your requirements might be too demanding for your community. Remember: streaks should build habits that enhance users' lives, not create obligations that make them dread your platform. # Points & XP Source: https://docs.nudj.cx/admin-guide/gamification/xp-currency Configure spendable points and progression XP — with level settings and admin adjustments Nudj has two currencies. They look similar but serve different roles. **Spendable**. Members buy rewards with points in the Shop, unlock key-gated challenges, and spend them on other transactions. **Progression**. Feeds levels, loyalty tiers, and leaderboards. Cannot be spent — it only ever accumulates. The UI refers to what used to be called **"XP settings"** as **"Level settings"** (#2242) — levels are derived from XP thresholds. The old standalone "XP settings" screen was restored under the new name (#648). ## Level settings Go to **Gamification -> Level Settings** to configure: * **Level thresholds** — XP required for each level. * **Level names and icons** — "Novice", "Expert", etc. * **Bonus XP for leaderboards** — additional XP awarded for leaderboard position. ### Bonus XP default Leaderboard rewards default **bonus XP to 0** (#688) so rewards don't accidentally inflate leaderboard scoring. Set it explicitly if you want bonus XP to fire. *** ## Earning points / XP Members earn through: * **Challenge completion** — distribution step on each challenge sets points/XP values. * **Actions** — some actions carry their own reward distribution. * **Achievements** — unlock rewards can include points/XP. * **Games** — score-to-currency conversion (see [Games](/admin-guide/gamification/games)). * **Admin adjustment** — see below. *** ## Admin adjustments Managers and above can adjust a member's balance directly (#1006): **Users -> select member -> Adjust Points / XP** Use cases: * Correct an earn bug. * Compensate for downtime. * Backfill after a data import. * Punish abuse (remove points). Adjustments show up in the member's transaction history. They're not silent — members can see them in their own wallet activity. *** ## Live balance updates Member wallets refresh balance live when points/XP are earned (#625 fixed stale-balance bugs). If you see a stale balance in a live demo, refresh the member app — don't wait for polling. *** ## Deleting members Deleting a member (#1006) removes: * Their profile and activity history. * Their points and XP balance. * Their reward wallet (unredeemed assets). Deletions are permanent. Use the Archive user flow if you only want to hide without data loss. *** ## Strategy — points vs XP | Goal | Currency | | - | - | | Drive shop purchases | Points | | Unlock paid content | Points (via key-gated challenges) | | Visible progression / status | XP + Levels | | Leaderboard ranking | XP (with optional bonus XP per event) | | One-off reward for milestone | Rewards + Points + XP combined | Use **points + XP on every reward**. Points give members immediate spending power; XP builds long-term engagement. The split ratio is your economy's most important dial. *** ## Next steps Turn XP into competitive rankings. Per-tier entry rewards and benefits. Reward consecutive-day behaviour. Visual mini-games tied to points/XP. # Archived & Featured Communities Source: https://docs.nudj.cx/admin-guide/getting-started/archived-communities Archive vs active, featured toggle, community status dropdown, restoration Communities have five statuses — **Draft**, **Live**, **Archived**, **Scheduled**, and **Expired** — controlled via the status dropdown (#810, #1502) on Community Settings. **Featured** is a separate toggle (not a status). This page covers the two special states: Archived and Featured. ## Archived communities Archiving a community hides it from members without deleting any data. Useful for: * Seasonal communities (holiday pop-up, product launch) * Deprecated brand experiences you might revive * One-off communities from past campaigns ### How to archive Community Settings -> Status -> **Archived**. Confirm the dialog. The community disappears from the member app immediately. ### The Archived Communities page Org Settings -> Communities -> **Archived** tab (#1038). Shows every archived community with: * Name, banner, member count at archive time * Date archived * Restore action (Admin+ only) ### Restoring Click **Restore** on any archived community. It flips status back to Draft (you then confirm to Live). All data comes back as it was. Members who were in the archived community see the community reappear. Depending on how long it was archived, consider whether to post a "we're back" announcement. *** ## Featured communities The **Featured** toggle (#899) surfaces a community in multi-community contexts: * Org-level landing page shows featured communities first * Community picker defaults to featured * Embed launcher highlights featured communities ### How to feature Community Settings -> **Featured** toggle -> on. Save. Any number of communities can be featured simultaneously. Ordering follows creation order unless you manually pin. *** ## Status matrix | Status | Visible to members? | In admin lists? | Data preserved? | | - | - | - | - | | Draft | No | Yes | Yes | | Live | Yes | Yes | Yes | | Scheduled | Not yet (goes live at a future date) | Yes | Yes | | Archived | No | [Archived page](#the-archived-communities-page) | Yes (#1038) | | Expired | No (window ended) | Yes | Yes | *** ## When to use each You're building. Not ready for members. Public. Members can join and participate. Seasonal end, deprecated, pausing with intent to maybe return. Data preserved, hidden from members. Community is configured but not yet visible to members — it goes live at a future date you set. A time-bounded community whose window has ended. Visible in admin but not to members. *** ## Best practices **Archive, don't delete.** Even if you're never coming back, the data is worth keeping for historical analytics and GDPR completeness. Feature your **top 3-5 communities** max. Featuring all communities = featuring none. # Control Room Introduction Source: https://docs.nudj.cx/admin-guide/introduction Your playbook for guiding communities, campaigns, and rewards Welcome to the Control Room—the place where community strategy and delightful execution meet. As Socrates said, "The only true wisdom is knowing you know nothing"—and that humility helps you listen to what your members actually want rather than assuming. ## The Theory Nudj turns audience engagement from wishful thinking into repeatable systems. Members join your branded world, complete challenges that matter, earn rewards they care about, and come back for the next chapter. **Why it works:** psychology beats guesswork, focused storytelling beats generic tools, and data beats hunches. To quote Aristotle, "Excellence is not an act, but a habit"—so build the systems and repetitions that make excellence habitual for your community members. ## What You’ll Learn * **Community Design**: Craft spaces people love visiting—no digital tumbleweeds allowed * **Challenge Creation**: Build activities members finish (and brag about) instead of abandoning * **Reward Systems**: Design incentives that motivate without feeling like bribes * **Analytics**: Read the numbers that matter so you can adjust with confidence ## Start Here Understand the structure you’re working with Launch your first community in minutes ## Explore the Platform Track the health of your community and member base Create the experiences members actually do Incentives that feel meaningful, not stale Guide members through progression paths Points, leaderboards, and streaks without the chaos Connect integrations and fine-tune the system ## Need Backup? Quick answers, troubleshooting, and links to contact support There are no silly questions here—only opportunities to build an experience your members will rave about. # Navigation Overview Source: https://docs.nudj.cx/admin-guide/navigation-overview Tour the admin shell: top nav, drill-down sidebar, community context, and + New button The Control Room has a consistent three-zone layout: **top nav** (global context), **drill-down sidebar** (navigation), and **main content** (whatever you're working on). A **floating AI chat** rides along the right edge on every page. ## Top nav Your uploaded org logo sits top-left. Click it to return to the org dashboard. If your account belongs to multiple orgs, switch between them here. Jumps you to the new org's dashboard, isolates the cookie, re-applies the new theme. Pick which community you're editing. Changing community swaps all the community-scoped content in the admin (challenges, rewards, achievements, theme). Unified create button. Pick what to make: Challenge, Reward, Achievement, Post, Leaderboard, Game, etc. Opens the Nudj Agent chat. See [Nudj Agent](/admin-guide/ai-agent). Light/dark mode, profile, sign-out. *** ## Drill-down sidebar The sidebar groups features into collapsible sections. Clicking a section **expands inline** — you don't leave the page until you click a specific destination. Top-level sections: | Section | Contains | | - | - | | **Dashboard** | Community home + live activity | | **Engagement** | Challenges, Rewards, Achievements, Posts, Feed, Messages, Referrals | | **Gamification** | Leaderboards, Streaks, Level Settings, Loyalty Tiers, Games, Currency | | **Users** | User list, analytics, rewards, achievement progress | | **Community** | Per-community settings (theme, rules, navigation layout) | | **Organisation Settings** | Org-level settings (team, branding, domains, integrations) | Studio/edit pages (challenge edit, reward edit, etc.) have their own **inner studio sidebar** with form steps and insights — separate from the main nav sidebar. *** ## Community context in the nav bar The top nav always shows which **community** you're currently editing. Switching community: * Rewrites the community cookie * Swaps in that community's theme (light vs dark mode, fonts) * Navigates to the community's `/admin` dashboard * Re-scopes every sidebar item to that community's content If you're managing multiple communities, pick the correct community **before** you click + New — otherwise you'll create the content in the wrong place. *** ## Main content area This is your working surface. Two patterns dominate: * **List pages** (Challenges, Rewards, Users): redesigned table with sticky columns, filter drawer, persisted sort, and bulk actions. * **Studio/edit pages**: left column is a **form-step sidebar** (e.g. *Basic Info / Content / Distribution / Scheduling*), right column is a **live preview** for themeable entities. See [Challenge Management](/admin-guide/challenges/management) for list-page controls and [Creating Rewards](/admin-guide/rewards/creating-regular-rewards) for studio-layout details. *** ## AI chat panel The right edge of every admin page holds a collapsible **AI chat panel**. Open it to ask the Nudj Agent to build, edit, or explain anything. See [Nudj Agent](/admin-guide/ai-agent). *** ## Mobile & responsive The shell collapses cleanly to tablet/mobile — the drill-down sidebar becomes a drawer, studio previews hide by default, and the top nav condenses to icons. Screenshots throughout these docs are taken at desktop width; mobile views are visually equivalent. # Organizations vs Communities Source: https://docs.nudj.cx/admin-guide/organizations-vs-communities The essential difference between your business account and your engagement spaces Think of it this way: if Nudj were a mall, your **organization** would be your business lease and admin office, while your **communities** would be the actual storefronts where customers shop. As Plato wrote, "The beginning is the most important part of the work"—and your community structure sets the foundation for everything that follows. **Your business account** Team management, billing, global settings. The backstage stuff that keeps everything running. **Where users actually go** Challenges, rewards, leaderboards. The fun experiences your users see and interact with. Most people care about communities (that's where the action is), but you need an organization first because someone has to pay the bills. **Your org might only have 1 community** — and that's totally fine. You still need to understand the difference because some settings live at the organization level (team, billing, integrations) while others live at the community level (challenges, rewards, members). Knowing which is which saves you from looking in the wrong place. ## Real Examples **Marketing Agency**: One organization called "Creative Spark Agency" might run communities for "Nike Summer Campaign", "Startup Pitch Contest", and "Holiday Social Challenge". **SaaS Company**: "TechCorp Inc" organization could have "Beta Testing Program", "Customer Onboarding Journey", and "Annual User Conference" communities. **The pattern**: One business account (organization) manages multiple user experiences (communities). ## The Setup Flow Your master business account with billing and team settings Individual campaigns or experiences for your users They never see organization details - just the communities they're part of ## Quick Decision Guide * **Adding team members?** → Organization settings * **Launching a new campaign?** → Create a community * **User access issues?** → Check community settings * **Billing problems?** → Organization settings If it's about running your business, it's organization-level. If it's about engaging users, it's community-level. ## Next Steps Set up your business foundation Launch your first user experience *Most people spend 5 minutes setting up their organization and hours crafting their communities. That's exactly how it should be.* # Post Best Practices Source: https://docs.nudj.cx/admin-guide/posts/best-practices Strategic approaches and proven techniques for creating engaging community content Master the art of community engagement through strategic posting that drives meaningful conversations and builds lasting connections. ## Post patterns These are editorial recipes, not a setting you pick. Posts have no "type" field, so every pattern below is built from the same handful of controls: the **Featured** toggle, the **Comments** and **Likes** toggles, scheduling, and campaigns. * **Setup**: Disable comments, enable likes, turn on **Featured** * **Strategy**: Clear, official tone with important information upfront * **Timing**: Peak community hours for maximum visibility * **Setup**: Enable comments and likes * **Strategy**: Ask open-ended questions * **Focus**: Think dinner party host, not debate moderator * **Setup**: Enable comments for questions, clear formatting * **Strategy**: Bullet points, examples, actionable takeaways * **Follow-up**: Create series for comprehensive learning paths * **Setup**: Link a campaign under **Advanced Settings**, coordinate timing * **Strategy**: Consistent messaging, clear CTAs * **Branding**: Match campaign visuals and voice ## Key Best Practices * Use conversational, approachable language * Lead with most important information * Include clear calls-to-action * Check how it reads in light and dark modes * High-resolution images (1440x1440px recommended) * Consistent branding and style * Keep essential words out of the image, since posts carry no alt text * Mobile-friendly formatting * Schedule during peak community activity * Convert to your own timezone first (see below) * Coordinate with campaigns and events * Maintain consistent posting rhythm * Create conversation opportunities * Moderate comments promptly * Feature community members * Watch likes and comments per post **Three limits worth knowing before you plan around them.** * **Scheduling has no timezone picker.** Start and end times follow your own browser, so if your audience sits in another region, do the conversion yourself before typing the time in. * **You can't reply to comments from the admin panel.** Moderation is delete-only. To answer a member, go into the community and reply as a member. * **Posts have no alt-text field.** Anything essential should live in the post text rather than baked into the image. ## Quick Tips **Content Calendar Ideas**: * **Monday**: Motivational content * **Wednesday**: Educational how-tos * **Friday**: Community highlights * **Weekend**: Discussion-focused posts **Engagement Techniques**: * Ask specific, thought-provoking questions * Share behind-the-scenes content * Celebrate member achievements * Pose a two-way question and let members answer in the comments. Posts have no poll, so the comment thread is the vote. **Measuring what works**: * The posts list and the post sidebar show **likes** and **comments** per post. That's the measure Nudj gives you today, so judge posts on it. * Gather community feedback regularly * Adjust strategy based on seasonal patterns **Don't plan around analytics that aren't there.** Posts have no A/B testing, no view counts, and no per-type performance breakdown. The **Total Views** and **Avg Engagement Rate** tiles on the posts analytics page are placeholders and always show a dash. Build your measurement around likes and comments. # Creating Posts Source: https://docs.nudj.cx/admin-guide/posts/creating-posts The four-step post studio: basic information, scheduling, interaction settings, and advanced settings Posts are community announcements, updates, and messages that appear in the member feed. Nudj redesigned the post creation flow (#1123) around the same studio layout used for challenges, rewards, and achievements. ## Start a post **Engagement -> Posts -> + New Post**, then choose **Start from blank** (or start from a template). You'll land in the post studio: step sidebar on the left, editor in the middle, preview on the right (#1963, #2148). The top bar holds the **status** dropdown and the save button, labelled **Create** on a new post and **Save** once it exists. The **language switcher** (#2226) appears in the top bar only on a saved post, and only when your community has more than one language enabled. A single-language community never sees it. *** ## The four steps Only **Basic Information** is required. The other three are marked **Optional** and can be left at their defaults. **Post Image** (1440x1440px recommended, square), the **Featured** toggle, **Post Title** (required), and **Post Content**, a rich-text editor (#675). Click the post image to open **Select Banner Image**, which has three tabs: **Library** (assets already in your community or organisation), **Upload New**, and **Image Gen AI**. Turn on **Schedule Post** to set a **Start Date & Time** and an optional **End Date & Time**. Leave it off to publish immediately. See [Featured & Scheduled Posts](/admin-guide/posts/featured-and-scheduled-posts). Toggle **Comments**, **Likes**, and **Post Image Display** on or off for this post. **Reset to Defaults** restores all three to comments on, likes on, and Post Image Display off. Four collapsible sections: **Tags**, **Campaigns**, **Custom Dark Mode Images**, and **Template Settings**. *** ## Basic Information Post studio Basic Information step **Post Title** is the only required field. It's marked with a red asterisk and the post won't save without it. **Post Content** is the rich-text body members read in the full-post view. **The toolbar you see is the short one.** Post Content opens with bold, italic, underline, bullet and numbered lists, and links. Click the **expand** icon at the toolbar's right for the full editor, which adds headings, blockquotes, tables, and inline images. Markdown shortcuts work either way, so typing `# ` starts a heading and `> ` starts a quote without leaving the compact view. The **Featured** toggle surfaces the post in the featured carousel. See [Featured & Scheduled Posts](/admin-guide/posts/featured-and-scheduled-posts). *** ## Scheduling Post studio Scheduling step Set a **Start Date & Time** to publish later, and an optional **End Date & Time** to take the post down automatically. The end-date calendar greys out any day before your start date. That guard works by day, not by time, so a same-day end time that's earlier than your start time can still be entered. Saving catches it with a "Start date must be before end date" error. Leave **Schedule Post** off and the post has no dates. The list shows it as **no dates set**, and a live post runs **live indefinitely**. *** ## Interaction Settings Post studio Interaction Settings step | Toggle | On | Off | | - | - | - | | **Comments** | Members can comment | Comments are blocked | | **Likes** | Members can like | Likes are blocked | | **Post Image Display** | Image is hidden from the post | Image shows on the post | **Post Image Display currently does the opposite of what it says.** The member-facing post inverts this flag, so members see your image when the toggle is **off** and lose it when you turn it **on**. The helper text under the toggle in the studio describes the intended behaviour, not the actual one. In practice: a new post starts **off**, so posts show their image by default, and **Reset to Defaults** returns it to off, which shows the image. Until this is fixed, read the toggle backwards and leave it **off** to keep your image visible. Turn **Comments** off for one-way announcements (policy changes, legal notices) where you don't want a discussion thread to moderate. *** ## Advanced Settings Post studio Advanced Settings step Categorise the post for filtering and organisation. Tags are organisation-scoped, so they're shared by every community in your org, and they show as a column on the posts list. Group posts into campaigns for organisation and reporting. Campaigns are org-scoped and can be created inline. Not currently functional. See the warning below. Configure how this post can be reused as a template. Choose which organisations may use it via **Available to Organisations**. Super admins additionally get a **Global Template** toggle that opens it to every organisation at once; with that toggle off, the organisation picker applies. **Don't use Custom Dark Mode Images on posts.** The separate dark upload is switched off in the post studio, so the control this section renders writes to the **same field as your Post Image**. Adding a "dark mode image" here replaces your regular post image instead of sitting alongside it. Posts currently use one image for both light and dark; leave this section alone until it's fixed. *** ## The preview panel The right-hand panel renders your post exactly as members see it, by loading the real member-facing page in a frame. **The preview does not update as you type.** It reflects the last **saved** version of the post, and refreshes only after you save. While you have unsaved edits, an amber **Unsaved** badge appears to tell you the preview is behind your changes. On a brand new post the panel reads **Save to see preview** for as long as you're on the create screen, and pressing **Create** does not populate it. The post saves and you're taken back to the posts list a moment later, so re-open the post from the list to see its preview. From then on you're in the edit studio, where the preview is populated and refreshes on every save. Starting from a **template** rather than **Start from blank** drops you straight into the edit studio, so the preview is there from the first screen. Use the device toggles at the bottom of the panel to check phone and desktop widths. *** ## Moderating comments The studio sidebar has a **Moderation -> Comments** section on saved posts (create the post first). Search comments, filter to top-level or replies only, show only comments with an image, delete individual comments, or **Export** them to CSV. You can also export every comment in the community from **Engagement -> Posts -> Export comments** (desktop only, capped at 10,000 comments per export). Moderation is **delete-only**. You can't reply to a comment from the admin panel, so if you want to answer a member you'll need to do it as a member in the community itself. Both the per-post section and the community-wide export need comment read permission, so some roles won't see them at all. *** ## When to use posts vs challenges vs achievements | Need | Use | | - | - | | Announce a new drop / release | Post (featured) | | Ask members to do something repeatable | Challenge | | Recognise passive behaviour | Achievement | | Send a private message to one member | Reward gifting with a "message" reward | *** ## Best practices Keep posts short. 1-3 paragraphs + one image is usually enough. If it's longer, link out to a blog post or dedicated landing page. Pair posts with challenges. The post announces a new thing, the challenge makes it actionable. Members see both in the feed together. See [Posts Best Practices](/admin-guide/posts/best-practices) for more strategy notes. # Featured & Scheduled Posts Source: https://docs.nudj.cx/admin-guide/posts/featured-and-scheduled-posts Feature posts into the carousel, schedule with start and end dates, and understand post statuses Posts can be **featured** (surfaced in the featured carousel) or **scheduled** (published at a future time). Both are configured in the post studio (#1123, #1963). ## Featuring a post Toggle **Featured** on in the studio's **Basic Information** step (#1930), or from the star icon on the posts list. Featured posts are pulled into a member-facing **featured posts carousel**, a swipeable row of posts shown alongside the feed. It's posts only, not a mixed row of challenges and rewards. **You can feature as many posts as you like.** Featuring a post does *not* unfeature the others. `featured` is an independent flag per post. The carousel fetches up to **5** featured posts, so a sixth featured post won't appear until one ahead of it drops out. **Rotate featured posts.** Because nothing auto-unfeatures, old featured posts stay featured forever until you turn them off. Audit the featured set periodically or the carousel fills with stale content and new posts never surface. *** ## Scheduling Post studio Scheduling step In the studio's **Scheduling** step, turn on **Schedule Post**: * **Start Date & Time**: when the post goes live. A future start date puts the post in **Scheduled**; at the start time it flips to **Live**. * **End Date & Time** *(optional)*: when the post comes down. Leave it empty and a live post runs **live indefinitely**. The end-date calendar greys out every day before your chosen start date. That guard works by day, not by time, so a same-day end time that's earlier than the start time can still be typed in. Saving catches it with a "start date must be before end date" error. A **Live** post can't have a future start date. Set a future date and you'll be asked to use **Scheduled** instead. This prevents accidental early publishes. *** ## Post statuses | Status | Meaning | | - | - | | **Draft** | Not visible to members. The default for a new post. | | **Scheduled** | Has a future start date; goes Live automatically at that time. | | **Live** | Visible in the member feed. | | **Expired** | Past its end date. No longer visible to members. | | **Archived** | Retired by hand; hidden from the feed. | The posts list shows counts for **Total Posts**, **Published**, **Draft**, and **Scheduled**, and lets you filter by status. A **Scheduled** post is flipped to **Live** by a background job at its start time, so you don't need to keep the tab open or publish it by hand. *** ## Pairing featured posts with content Featured posts are most effective when paired with actionable content: * Featured post announces a new challenge -> challenge links from the post. * Featured post introduces a new reward -> member sees and taps to check out the reward. * Featured post for a time-limited event -> urgency + one-click CTA. *** ## Best practices Use an **end date** for time-limited announcements (an event, a sale). The post expires on its own instead of needing a manual cleanup pass. Use scheduled posts to **time content with timezones**. 8am local time in your biggest region is usually the engagement sweet spot. See [Creating Posts](/admin-guide/posts/creating-posts) for the full studio flow. # Reward Advanced Features Source: https://docs.nudj.cx/admin-guide/rewards/advanced-features The Advanced Settings step, reward statuses and scheduling, cross-community rewards, supply sharing, and who can do what The optional **Advanced Settings** step holds the long-tail configuration most rewards never need. This page covers those sections plus the advanced concepts that live elsewhere: statuses and scheduling, defaults, and role permissions. Reward studio Advanced Settings step ## Auto-Redeem "Rewards redeem themselves the moment they're earned. No button clicking needed." Turn it on for instant-value rewards where a claim step is friction. * Applies to **asset** rewards when the asset is earned or bought. * **Badges and boosts force it on** (their card is hidden - it's how they auto-award). * Auto-redeem **silently skips** if the reward collects [fulfilment details](/admin-guide/rewards/redemption-and-fulfilment) - those need the member's input at redemption. * **Giveaway prizes don't auto-redeem**: winners claim their won reward from the wallet themselves. *** ## Tags, Accent Color, and Metadata * **Tags** - organisation-scoped labels for filtering and reporting. * **Accent Color** - an optional color for the reward's promotional card backgrounds in the widget. * **Metadata** - custom key-value pairs (unique keys) for anything you want exposed to integrations and webhooks. *** ## Asset Supply Sharing "One supply, shared across all allocations. Everyone pulls from the same pot." With **Shared Asset Supply** on, the reward keeps a single supply pool no matter how many ways it distributes (challenges, shop, gifts) instead of a separate cap per allocation. Shown for asset-based rewards only. *** ## Additional Communities "Use this exact same reward (same supply and all) in other communities across your org." Pick extra communities and the **same reward document** - including its one supply pool - serves all of them. In the additional communities the reward is **read-only**: to edit it, open it from the community it was created in (the form shows a banner pointing you there). *** ## Custom Dark Mode Images **Don't use this section on rewards yet.** The separate dark-mode upload is currently disabled here, so the control writes to the **same field as the main reward image** - uploading a "dark mode image" replaces your reward image instead of adding a dark variant. Rewards use one image for both themes for now; leave this section alone until it's fixed. *** ## Template Settings "Control how this reward can be reused as a template." Choose which organisations may create copies from it via **Available to Organisations**; the original is never modified by copies. Super admins additionally get a **Global Template** toggle that opens it to every organisation at once. *** ## Statuses and scheduling A reward's lifecycle is its [status](/admin-guide/rewards/creating-regular-rewards#reward-status): **Draft** (hidden while you build), **Live**, **Scheduled** (goes Live automatically at its start date), **Archived** (retired, kept for records), and **Expired**. Draft and Archived rewards are never shown to members. There is no per-attachment scheduling on challenges - timing is controlled on the **reward** itself. Status compatibility keeps combinations sane when attaching a reward to a challenge: | Challenge status | Accepts rewards that are | | - | - | | Live | Live only | | Scheduled | Scheduled or Live | | Draft | Draft, Scheduled, or Live | So for a staged launch, attach a **Scheduled** reward to a Draft or Scheduled challenge and both flip Live on their start dates. There is also no "delete" for rewards - retire a reward by setting it to **Archived**. *** ## Gifting-only rewards There's no "manual distribution" toggle. To make a reward admin-gifted only, simply **don't attach it** to any challenge or the shop, and hand it out via [Gift Rewards](/admin-guide/rewards/reward-gifting). Gifts still consume the reward's supply and respect its per-user cap. *** ## Defaults quick reference A new Regular reward starts with: | Setting | Default | | - | - | | Status | Draft | | Earning window | Today, for 7 days | | Total supply | Unlimited | | Max per user | 1 | | Max entries per user (giveaways) | 5 | | Auto-redeem | Off (on and locked for badges/boosts) | *** ## Who can do what | Role | Rewards | | - | - | | Viewer | View only | | Moderator | View only | | Manager | Create, edit, archive, run manual giveaway draws | | Admin / SuperAdmin | Everything, including gifting | **Gifting is Admin-and-above** - it awards currency alongside assets, which Managers can't create. Viewers see reward pages in a read-only state. *** ## Next steps The studio walkthrough, including the status dropdown. Hand-deliver rewards to specific members. Auto-redeem vs manual claiming in practice. Draw mechanics and winner selection. # Reward Analytics Source: https://docs.nudj.cx/admin-guide/rewards/analytics The per-reward Stats page (Overview, Allocations, Users, Fulfilment) and the community-wide rewards analytics Every reward has a dedicated **Stats** page: from the rewards list, click a reward's **View stats** action (or visit `/admin/engagement/rewards/{id}/stats`). It has four tabs, plus a shared date-range filter. Reward Stats page Overview tab with Entries Summary and Participation Timeline ## The four tabs ### Overview * **Claim funnel** - Distributed -> Claimed -> Redeemed, with an expiry notice when unredeemed rewards lapse. * **Supply donut** - how much of the pool is used. * **"Where people won it"** - the distribution source breakdown: challenge, achievement, manual (gifts), shop, giveaway, leaderboard. * **Timelines** - "Claims & Redemptions Timeline" for asset rewards, "Participation Timeline" for giveaways. * For giveaways, an **Entries Summary** replaces the funnel: entries distributed, unique participants, average entries, and winners (with "draw pending" before the draw). ### Allocations Every way the reward distributes, as a table: source, status, per-user amount, distributed count, and pool. Gift batches appear as **Manual Reward Distribution**; shop listings as **Shop Purchase**. ### Users Every holder (or entrant), searchable, with **Export Users CSV**. For giveaways this tab adds entry counts, winner status, and - for manual giveaways - the winner assignment flow and a tracking-template CSV. ### Fulfilment The read-only record of every redemption with the delivery details members submitted. See [Redemption & Fulfilment](/admin-guide/rewards/redemption-and-fulfilment). *** ## Community-wide analytics For the whole community at once, **Report -> Analytics** includes a Rewards section: total rewards, assets claimed, giveaway entries, and distribution events, plus **Top Assets** (by claims, with redemption rate) and **Popular Giveaways** (by participants) over a selectable period. *** ## Reading the numbers Members earn but don't claim. Check perceived value, redemption friction, and whether a code list ran out mid-campaign - the timeline shows a cliff if so. If gifts dominate the source breakdown, the automatic earn paths aren't pulling - revisit which challenges distribute this reward. A handful of members hold most giveaway entries. Review per-challenge entry amounts and the reward's Max Entries Per User cap. *** ## API access For custom dashboards, the [Analytics API reference](/api-reference/analytics) covers the rewards endpoints behind these tabs: allocations, redemptions, distribution, giveaways, recipients, timeline, and fulfilments. # Badges Source: https://docs.nudj.cx/admin-guide/rewards/badges Collectible emblems members earn once and keep: the 3-step badge wizard, award paths, and where badges display Badges are collectible emblems members **earn once and keep**. There's nothing to redeem: the moment a badge is earned it lands in the member's collection, no codes or claim step involved. ## Creating a badge Pick **Badge** in the [type picker](/admin-guide/rewards/reward-types). Because badges have nothing to supply, redeem, or fulfil, the wizard slims to **three steps**: Badge Basic Information step with the three-step sidebar **Badge Image** (1:1 aspect ratio, ideally **512 x 512px**, cropped as a rounded square), plus the required title and description. PNG, JPG, GIF, or WebP. The **Featured** toggle is available; Spotlight isn't (badges award themselves, so there's nothing to spotlight for claiming). Just the **Earning Window** (start and end). No redeem-by deadline exists for badges. Tags, accent color, template settings, metadata, and a dark-mode image variant. Badge Dates step showing only the Earning Window Supply is handled for you: badges are created with **unlimited supply, capped at one per member**. Rarity comes from how hard the badge is to earn, not from scarcity. *** ## How members earn a badge * **Challenges** - attach the badge on a challenge's **Distribution** step. * **Achievements** - the classic path: attach it on an achievement's **Rewards** step so it marks a milestone. * **The shop** - badges can be listed in the [Reward Store](/admin-guide/rewards/shop-integration) for a points price (quantity is fixed at one). * **Gifting** - hand one to specific members via [Gift Rewards](/admin-guide/rewards/reward-gifting). However it's earned, the badge **auto-awards instantly** - the member sees it in their collection right away. **Live badges aren't secret.** Members can see every live badge as a locked, grayscale entry in their profile and wallet galleries before earning it. That's great for aspiration, but don't publish a surprise badge until you want it teased. *** ## Where badges display * The member **profile page** (badges carousel) and the dedicated **Badges** page in the member app. * The **widget profile drawer**: an earned/total count plus a badge grid, with unearned badges in grayscale. * The **widget wallet** has a badges section and filter. * Earning a badge from a challenge triggers a **badge unlock celebration** in the completion flow. Give badges clear, descriptive titles - the title doubles as the image's screen-reader text, and it's the label members see everywhere the badge appears. *** ## Badge analytics A badge's **Stats** page works like any reward's: the Overview shows the claim funnel and a **Claims Timeline**, the **Users** tab lists every holder, and the config chips mark it as a **Digital Badge**. (A Fulfilment tab renders too, but badges never collect fulfilment details, so it stays empty.) *** ## Tiered badge design Create a series of escalating badges to give members a ladder to climb: | Tier | Example | | - | - | | Bronze | "Newcomer" - first challenge completed | | Silver | "Regular" - ten challenges completed | | Gold | "Champion" - fifty challenges completed | Use [achievements as actions](/admin-guide/achievements/achievements-as-actions) to gate each tier behind the previous one. *** ## Next steps Badges next to the other four types. The natural home for badge rewards. Sell badges for points. Track who's earned what. # Creating Rewards Source: https://docs.nudj.cx/admin-guide/rewards/creating-regular-rewards The reward studio, step by step: basic information, dates, supply, redemption, fulfillment, and advanced settings Rewards use the same **studio layout** as challenges, achievements, and posts: a step sidebar on the left, the form in the middle, and a live preview on the right. ## Start a reward **Engagement -> Rewards -> Create Reward** (or the top-nav **New -> Reward** menu item) opens the reward flow. You first land on the **template library** - pick a template, or choose **Create blank** to open the type picker. Reward template library with the Start blank option Reward type picker showing the five reward types There are **five** blank types - the picker scrolls to show them all: | Type | What it is | | - | - | | **Regular Reward** | Claimable with points or requirements. Discounts, merch, digital goods. | | **Giveaway Reward** | A prize draw - winners picked randomly or by hand. See [Giveaways](/admin-guide/rewards/sweepstakes-giveaways). | | **Badge** | A collectible emblem for achievements or milestones. See [Badges](/admin-guide/rewards/badges). | | **XP Boost** | An effect that multiplies a member's XP for a set time. | | **Points Boost** | An effect that multiplies a member's points for a set time. | Once you pick a type it's **locked in** - you can't change a reward's type after creation. The type you pick changes which steps appear. **Badge**, **XP Boost**, and **Points Boost** rewards have nothing to supply, no redemption format, and nothing to fulfil, so they skip the Supply, Redemption, and Fulfillment Details steps. XP and Points Boosts add their own **XP Boost Config** / **Points Boost Config** step instead. *** ## The steps A **Regular** or **Giveaway** reward has **six** steps. Only the first four are required; the last two are optional and can be left at their defaults. Move through them with the **Next Step** button in the sidebar - on the final step it reads **Done**. Reward image, title, and description. **Required.** The earning window and the redeem-by deadline. **Required.** How many exist and how many each member can claim. **Required.** How members actually claim the reward. **Required.** Optional info collected from members at redemption (size, address, etc.). Auto-redeem, tags, metadata, cross-community scope, and more. The top bar keeps a **status** dropdown (starting on **Draft**) next to the **Create** button. *** ## Basic Information Reward studio Basic Information step **Title** and **Description** are both required (each marked with a red asterisk). Description is a rich-text editor - at least 10 characters, up to **3,000** - and the toolbar's **expand** icon opens a larger fullscreen editor. Click the **Reward Image** tile to open the image picker (best results at **1440x1080px**). It has three tabs: **Library** (assets already in your community or organisation), **Upload New**, and **Image Gen AI**. The left column also has a **Featured** toggle (surfaces the reward prominently in the member app) and, on manually-claimed rewards, a **Spotlight** toggle. Auto-redeeming rewards like badges and boosts hide Spotlight. Shop availability is set later, in the [Supply](#supply) step. Tags, metadata, and the accent colour are **not** on this step - they live under [Advanced Settings](#advanced-settings). The content-language switcher appears in the sidebar only after the reward is saved. *** ## Dates Reward studio Dates step showing Earning Window and Use-It-or-Lose-It Date * **Earning Window** - a **Start Date & Time** (when members can begin earning) and an **End Date & Time** (when earning closes). Members who earned the reward before the window closes keep it and can still redeem it. * **Use-It-or-Lose-It Date** - the redeem-by deadline (required). After this date, any **unredeemed** reward expires, no matter how the member got it. * **Redemption Viewing Window** - how long a member can still view their code or details after redeeming (preset durations or custom). For a **Giveaway**, this step relabels to the entries-open / entries-close window and adds the **draw date** and winner-selection method. *** ## Supply Reward studio Supply step showing Unlimited Total Supply and Limited Per User * **Unlimited Total Supply** - on by default (no cap). Turn it off to set a hard limit on how many can ever be claimed. * **Limited Per User** - cap how many one member can claim. For a **Giveaway**, this step instead sets the **number of winners**, **max wins per user**, total **entries** supply, and **max entries per user**. A reward's **Shop Settings** also live here, but the button is disabled until the reward is saved (it needs an ID first). Save the reward, then open Shop Settings to set a point price. See [The Reward Store](/admin-guide/rewards/shop-integration). *** ## Redemption Choose a **Display Mode**: a polished **Text-Based Message** or scannable **Code-Based Redemption**. Reward studio Redemption step in Text-Based Message mode Switch to **Code-Based Redemption** to hand out codes in one of four formats - **Text Code**, **Link**, **Barcode**, or **QR Code** - and decide whether everyone shares **one code** or **each user gets their own** from an uploaded list. Reward studio Redemption step in Code-Based Redemption mode Two optional extras sit below: **Add Code Prefix** (prepended to every code - turns `SAVE20` into `brand.com/SAVE20-ABC123` for links) and **Add Instructions** (extra steps members see when redeeming). For a per-user code list, paste or CSV-upload up to **500,000 codes** (max 100 characters each). Re-uploads merge with the existing list and drop duplicates. Keep your own backup - Nudj stores codes but doesn't expose an export. *** ## Fulfillment Details Reward studio Fulfillment Details step, empty state This optional step collects extra information from a member **at the moment they redeem** - not when they earn or buy the reward. Use **Create New Variable** to add a field, or **Select a field** to reuse one you've defined before. Typical uses: T-shirt size and shipping address for merchandise, attendee name for event tickets, or a delivery address for physical items. Whatever you collect here shows up later in the reward's **Fulfilment** tab, where you confirm and action each redemption. See [Redemption & Fulfilment](/admin-guide/rewards/redemption-and-fulfilment). *** ## Advanced Settings The optional **Advanced Settings** step is a stack of collapsible sections for the long-tail config most rewards don't need: Reward studio Advanced Settings step Claim the reward automatically the moment it's earned - no member action. See [Advanced Features](/admin-guide/rewards/advanced-features#auto-redeem). Organisation-scoped labels for filtering and reporting. A per-reward accent colour used in the member app. A key-value map for arbitrary data you want exposed to integrations and webhooks. Scope the reward to more than one community in your org. See [Advanced Features](/admin-guide/rewards/advanced-features#additional-communities). **Template Settings**, **Asset Supply Sharing**, and **Custom Dark Mode Images**. *** ## Reward status The **status** dropdown in the top bar controls whether a reward is visible and distributing. It has five states: **Draft** (hidden while you set it up), **Live** (visible to members now), **Scheduled** (publishes on the start date), **Archived** (hidden and retired, kept for records), and **Expired** (past its end date). Reward status dropdown showing all five statuses Save any reward as **Draft** while you finalise copy or wait on assets - a Draft reward **won't distribute even if it's attached to a Live challenge**. Flip it to **Live** when you're ready. *** ## Validation gotchas * **Redemption config is required** for a claimable reward before it can go Live - a text message, a code list, a URL, or instructions. * A finite reward with **zero remaining supply** saves but can't distribute - check the Supply step before launch. * The **Use-It-or-Lose-It Date** is required on Regular and Giveaway rewards; Badge, XP Boost, and Points Boost rewards don't have one. Validation errors surface inline on the offending step with an actionable message. *** ## Next steps The full taxonomy of reward shapes. How members redeem, and where you confirm fulfilment. Auto-redeem, cross-community, draft rewards, scheduling. Let members buy rewards with points. # Rewards System Overview Source: https://docs.nudj.cx/admin-guide/rewards/introduction An introduction to Nudj rewards: how they work, the five reward types, and where to start Rewards are how you say "thank you" to members who engage with your community. They drive participation, build loyalty, and create moments worth sharing. Every reward follows the same journey: you **create** it, members **earn or buy** it, they **redeem** it, and for anything physical you **fulfil** it. The pages in this section walk each stage. ## The five reward types When you create a reward, you pick one of five types. The type decides which setup steps you see and how members get the reward. Claimed directly by members. Discount codes, merch, digital downloads. A prize draw. Members earn entries; you draw winners automatically or by hand. A collectible emblem earned once and kept. Pure recognition, nothing to redeem. An effect that multiplies a member's XP for a set duration. An effect that multiplies a member's points for a set duration. [See Reward Types](/admin-guide/rewards/reward-types) to compare all five and the allocation and redemption options underneath them. ## Where to start 1. Read [Creating Rewards](/admin-guide/rewards/creating-regular-rewards) for the studio walkthrough. 2. Create a simple **Regular Reward** with a text message or code. 3. Save it as **Draft**, then flip to **Live** when you're ready. 1. Reward points and XP from your challenges. 2. Add **Badges** for milestones and set up [Loyalty Tiers](/admin-guide/rewards/loyalty-tiers) with per-tier multipliers. 3. Open the [Reward Store](/admin-guide/rewards/shop-integration) so members can spend their points. 1. Run a [Giveaway](/admin-guide/rewards/sweepstakes-giveaways) for a headline prize. 2. Attach it to your live challenges so completions earn entries. 3. Announce winners with a post after the draw. 1. Use [Gift Rewards](/admin-guide/rewards/reward-gifting) to send a reward, points, or XP to specific members. 2. Search or upload a CSV of recipients. 3. Confirm and send. It's immediate. ## Explore the section The six-step reward studio, start to finish. How members redeem, and where you view delivery details. Let members spend points on rewards. Track distribution, claims, and redemptions. **Start simple.** Create one working reward and watch it flow end to end before reaching for advanced configuration. Once you see how it works, everything else makes more sense. # Loyalty Tiers Source: https://docs.nudj.cx/admin-guide/rewards/loyalty-tiers VIP tier progression by points earned or amount spent, with per-tier multipliers, perks, and entry rewards Loyalty tiers bucket members into VIP levels based on **points earned** or **amount spent** (your choice of progression metric). Each tier can boost the member's earning power and greet them with a one-time entry reward. Configure them at **Gamify -> Loyalty Tiers**. Loyalty Tiers configuration page with tier definitions ## Setting up tiers Tiers start **off with no tiers defined**. Enable them and either build tiers by hand or pick one of the **presets** (Classic VIP's Bronze/Silver/Gold/Platinum, Starter Pack, Fan Tiers, Retail Rewards, and more). Up to 10 tiers. **Points Earned** or **Amount Spent**. This is what moves members up tiers. Per tier: a name and color, the **entry requirement** (the metric threshold), optional **Points Bonus** and **XP Bonus** multipliers, a list of **perks** (display copy members see), and an optional **entry reward**. Members are evaluated as they earn or spend: crossing a threshold **promotes them automatically**. There's no demotion - tiers are lifetime. *** ## What a tier actually grants * **Points and XP multipliers** - the mechanical perk. A tier's bonus applies to everything the member earns while in it (if they also hold a temporary [boost](/admin-guide/rewards/reward-types), the highest multiplier wins). * **A one-time entry reward** - bonus XP, points, and/or a reward delivered **once** when a member first enters the tier. * **Perks** - free-text benefits shown to members (early access, exclusive support). Delivering on them is up to you - there's no built-in tier gating of challenges or content. The widget's loyalty-tiers section has its own **Styling** tab for how tiers look to members; tier progress uses the tier metric, not XP levels (levels are a separate system configured under XP & Leveling). *** ## Tier insights The Loyalty Tiers page's **Insights** tab shows tier distribution across members, movement over time, revenue summary, and member engagement per tier. Community-wide loyalty stats also appear under **Report -> Analytics**. *** ## Design tips Keep it to **3-5 tiers**, and make tier 2 reachable within a couple of weeks of joining - members who feel early progress keep climbing. Make multipliers meaningfully different per tier (say +10% / +20% / +35%) so promotion feels like a real upgrade, and use each tier's entry reward as the celebration moment. *** ## Next steps Boosts vs tier multipliers. Somewhere to spend those multiplied points. Pair tier entry rewards with prize draws. Loyalty stats in Report -> Analytics. # Redemption & Fulfilment Source: https://docs.nudj.cx/admin-guide/rewards/redemption-and-fulfilment The last mile: how members redeem an earned reward, and where you view the delivery details to fulfil it Creating a reward is only the first half of the journey. This page covers the second half: a member **redeems** the reward they earned, and (for anything you have to deliver) you **fulfil** it using the details they submit. ## How members redeem An earned reward lands in the member's **Wallet**. Opening it shows a **Redeem** button. If the reward has [Fulfillment Details](/admin-guide/rewards/creating-regular-rewards#fulfillment-details), redemption starts by asking the member for them, one field at a time. Required fields must be filled before they can continue; optional ones can be skipped. The member confirms, and Nudj marks the reward redeemed. What they see next depends on the reward's redemption format: a **text code**, a **link**, a **barcode**, a **QR code**, a **Shopify discount**, or a **rich-text message**. XP and Points Boosts don't show a code, they activate automatically. Delivery details are collected **at redemption**, never when the member earns or buys the reward. A member only fills them in the moment they claim. *** ## The redeem-by deadline The **Use-It-or-Lose-It Date** (set on the [Dates](/admin-guide/rewards/creating-regular-rewards#dates) step) is the deadline to redeem. After it passes, an unredeemed reward can't be claimed: the Redeem button is disabled and the reward shows as **expired**. Don't confuse the two timers. **Use-It-or-Lose-It** is the deadline to *redeem*. The separate **Redemption Viewing Window** (set on the reward's [Dates](/admin-guide/rewards/creating-regular-rewards#dates) step) controls how long the code stays visible *after* redeeming, so a member can come back to their code for a while before it's hidden. "Missed out" and "Sold out" are different: those appear on **shop / browse cards** before a member owns the reward (the supply ran out). They aren't redemption states of a reward already in someone's wallet. *** ## Where you confirm fulfilment For rewards you deliver yourself (merch, event tickets, gift cards), open the reward, go to its **Stats** page, and select the **Fulfilment** tab: ```text theme={null} /admin/engagement/rewards/{reward-id}/stats?tab=fulfilment ``` Reward Stats page with the Fulfilment tab selected, showing the empty state The tab lists **every redemption of the reward**, with three columns: | Column | What it shows | | - | - | | **Member** | Who redeemed: avatar, display name, and email | | **Redeemed** | When they redeemed | | **Delivery details** | The fields they submitted at redemption (e.g. shipping address, T-shirt size) | **The Fulfilment tab is a read-only record.** It surfaces who redeemed and what they submitted so you can fulfil the reward in your own systems (ship the item, email the gift card). There is no "mark as fulfilled" toggle or status, and no export: Nudj shows you the details, you handle delivery out-of-band. Until someone redeems, the tab reads *"No one has redeemed this reward yet. Delivery details appear here once a member redeems."* A redemption with no details recorded shows the reason in its row (either the reward doesn't collect details, or none were submitted). Because the rows contain member contact details, viewing the Fulfilment tab needs permission to read **both rewards and members**. A role without member-read access sees an error instead of the table. *** ## Setting up what to collect The fields on the Fulfilment tab come from the reward's **Fulfillment Details** step. Add a field for each thing you need to deliver the reward. Reward studio Fulfillment Details step Common setups: T-shirt size and shipping address for merchandise, attendee name for event tickets, a delivery address for physical items. *** ## Next steps Set up redemption and fulfilment details. Allocations, redemptions, and timelines. Which rewards need redemption vs auto-apply. Let members buy rewards with points. # Gift Rewards Source: https://docs.nudj.cx/admin-guide/rewards/reward-gifting Send rewards, bonus points, or XP directly to selected members: search or CSV, then confirm **Gift Rewards** sends rewards straight to members you pick, skipping the normal earn flow. It also covers what used to be called "reward drops": one flow handles a single VIP or a 500-person list, and it always distributes **immediately**. Gift Rewards flow with Select Users step, Drop List, and CSV upload ## Where to start Any of these opens the same flow: * **Engagement -> Rewards** -> the **Gift Rewards** button. * **Users** -> the **Gift Rewards** button. * A member's stats page -> the **...** menu -> **Gift Reward**. * The new-reward screen's **Gift or drop rewards** card. Gifting needs **Admin or above** - it can grant currency alongside rewards, which Manager roles can't create. ## The flow Search members **by name or email** and add them to the Drop List one by one, or **Upload CSV** with up to **500 rows** of emails or usernames (a sample file is provided). Pick one or more rewards, and optionally add **Bonus XP** and **Points** per recipient. You can also send only points/XP with no reward at all. The confirmation summarises recipients, currency, and rewards. Gifting is immediate and **can't be undone**. After sending, a summary reports how many members were processed and lists any that failed. ## Rules to know * **Supply is checked up front**: if the reward doesn't have enough remaining supply for every recipient, the whole gift is rejected before anything sends. * **Per-user caps are respected quietly**: a member already at the reward's per-user limit is skipped for that reward (their bonus points/XP still arrive) - no error is raised. * Gifts consume the reward's normal supply pool and appear in the reward's [Allocations](/admin-guide/rewards/analytics) as **Manual Reward Distribution**. For a reward that should *only* ever be gifted, just never attach it to a challenge or the shop - there's no special toggle needed. See [Advanced Features](/admin-guide/rewards/advanced-features#gifting-only-rewards). ## Adjusting points directly Separate from gifting: on a member's stats page, the **...** menu has **Manage Points** for crediting or debiting a member's points balance (Admin+; there is no XP adjustment). Use it for corrections rather than sending a gift. ## Next steps Something worth gifting. Supply sharing and per-user caps. Let chance pick the winners instead. Where gifts show up in the numbers. # Reward Types Source: https://docs.nudj.cx/admin-guide/rewards/reward-types The five reward types - Regular, Giveaway, Badge, XP Boost, Points Boost - and the allocation and redemption dimensions underneath them When you create a reward from blank, Nudj asks you to pick one of **five** types. The type is locked once the reward is created, and it decides which setup steps you'll see. Reward type picker: Regular Reward, Giveaway Reward, Badge, XP Boost, Points Boost ## The five types A first-come-first-serve reward members claim directly - discount codes, merch, digital downloads. The everyday reward. See [Creating Rewards](/admin-guide/rewards/creating-regular-rewards). A prize draw. Members earn **entries** over a window; you draw winners randomly or by hand at the draw date. See [Giveaways](/admin-guide/rewards/sweepstakes-giveaways). A collectible emblem earned once per member - pure recognition, nothing to redeem. See [Badges](/admin-guide/rewards/badges). An effect that **multiplies the XP** a member earns for a set duration. Activates automatically when earned, then multiplies their next earns until it expires. An effect that **multiplies the points** a member earns for a set duration. Activates automatically when earned, then multiplies their next earns until it expires. **Badge, XP Boost, and Points Boost skip most of the setup.** They have nothing to supply, no redemption format, and nothing to fulfil, so their wizard drops the Supply, Redemption, and Fulfillment Details steps (XP/Points Boosts add a Boost Config step instead). *** ## Two dimensions underneath A Regular or Giveaway reward is defined by two independent choices. ### Allocation - what the member gets Supply step showing Unlimited Total Supply and Limited Per User An **owned** item with a finite (or unlimited) supply - the member holds it in their wallet and redeems it. Regular Rewards are assets. **Lottery tickets** for a giveaway - the member accumulates them and the reward resolves at the draw. Giveaway Rewards are entries. ### Redemption format - how they claim it Redemption step in Code-Based Redemption mode showing the four code formats Set on the [Redemption](/admin-guide/rewards/creating-regular-rewards#redemption) step for asset rewards: * **Text message** - a rich-text message shown when they claim. * **Code** - a **Text Code**, **Link**, **Barcode**, or **QR Code**, either one shared code or a unique code per member. * **Shopify discount** - for communities connected to a Shopify store. Badges and boosts have no redemption format - they resolve automatically. *** ## Points & XP are currencies, not reward types Members earn **points** (spendable) and **XP** (progression) by completing challenges and actions - you set those amounts on the **challenge**, not as a separate reward. What lives in the reward catalogue are the two **boost** rewards that temporarily amplify them: | Currency | What it does | How to hand it out | How to amplify it | | - | - | - | - | | **Points** | Spendable at the [Shop](/admin-guide/rewards/shop-integration) | Challenge/action rewards | **Points Boost** reward | | **XP** | Progression through levels | Challenge/action rewards | **XP Boost** reward | *** ## Placement & distribution (not types) These are options *on* a reward or ways to hand it out - not separate types: * **Featured** and **Spotlight** - toggles on the [Basic Information](/admin-guide/rewards/creating-regular-rewards#basic-information) step that surface a reward more prominently. (Spotlight only appears on manually-claimed rewards - badges and boosts hide it.) * **Shop** - list a reward in the [Reward Store](/admin-guide/rewards/shop-integration) so members can buy it with points. * **Gifting** and **giveaway draws** - ways to *distribute* a reward. See [Gift Rewards](/admin-guide/rewards/reward-gifting). * **Cross-community scope** - the `additionalCommunityIds` field (in [Advanced Settings](/admin-guide/rewards/advanced-features#additional-communities)) makes one reward redeemable across several communities in your org. *** ## When to use which | Goal | Pick | | - | - | | Hand out discount codes | **Regular Reward** with a code list | | Give away a big prize | **Giveaway Reward** with a draw date | | Celebrate a milestone visibly | **Badge** | | Run a double-XP weekend | **XP Boost** | | Run a double-points promo | **Points Boost** | | Let members buy a reward with points | Any asset reward, listed in the [Reward Store](/admin-guide/rewards/shop-integration) | *** ## Next steps The step-by-step studio walkthrough. Entry-based prize draws. Collectible emblems. Let members spend points. # The Reward Store Source: https://docs.nudj.cx/admin-guide/rewards/shop-integration The points shop: list rewards for purchase, set prices and stock, and let members spend their points The **Reward Store** is where members spend **points** on rewards. You curate it from **Engagement -> Reward Store** in the admin sidebar. Reward Store admin page with shop items, prices, and supply Each card shows the reward's **points price**, its remaining **supply**, an **Available in shop** switch, and **Edit** / **Remove** actions. *** ## Turning the shop on or off The shop switch is **organisation-wide**: the **Rewards Shop** toggle in **Organisation Settings -> Features** (or the **Shop** pill at the top of the Reward Store page itself). Turning it off hides the member-facing shop across your org: the Shop nav item disappears, and prices, checkout, and purchase controls are suppressed, so members can no longer browse to or buy from the shop. There is no per-community shop toggle. The Community Settings Features tab controls points, leaderboards, posts, and similar, but not the shop. *** ## Listing a reward in the shop Two ways in: Click **Add to Shop**, pick any non-expired reward that isn't already listed, and set its shop terms. On the reward's [Supply](/admin-guide/rewards/creating-regular-rewards#supply) step, the **Shop Settings** card opens the shop terms for a listed reward, or takes you to the Reward Store to list it. The button is disabled until the reward has been saved once. The shop terms are: Edit Shop Item dialog with Available in Shop switch and Points Price * **Points Price** - what a member pays per unit. * **Available in Shop** - show or hide the listing without removing it. * **Limited Shop Supply** - cap how many can be bought from the shop (hidden when the reward shares one supply pool across all its allocations). The dialog also shows how many are already bought vs still available. **Per-member purchase caps are not a shop setting.** They come from the reward's own Supply step (**Limited Per User**). A member at their cap can't buy more, no matter what the shop terms say. *** ## How members buy Members open **Shop** in the main navigation of the member app. It lists every **live** reward with an active shop listing; the detail view shows the points price, remaining stock, and a purchase button. Purchases: * Deduct the points and place the reward straight into the member's **wallet**. * Are capped per purchase (up to 5 at a time) and by the reward's per-user limit. * Fail cleanly when points or stock run short. *** ## Purchase safeguards Every purchase runs as a single transaction with duplicate-tap protection, and the checks run in a safe order: * **Per-user cap is checked before any points are taken** - hitting the cap never costs the member points. * **Insufficient balance** and **insufficient stock** are both blocked with a clear error. * Shop purchases and challenge distributions both update supply transactionally, so stock counts stay consistent. *** ## Shop purchases in analytics On a reward's **Stats** page, shop activity is broken out as its own source: the Overview's "where people won it" chart has a **shop** slice, and the Allocations tab labels shop listings as **Shop Purchase**. See [Reward Analytics](/admin-guide/rewards/analytics). *** ## Pricing tips Price rewards so a member can afford something meaningful after 2-3 challenges. Too expensive and the shop feels out of reach; too cheap and points feel worthless. Mix always-available shop rewards with limited-time challenge-earned rewards. The variety keeps both the shop and the wallet feeling fresh. *** ## Next steps Set supply and per-user caps on the Supply step. What happens after a member buys or earns. Points are the spendable currency behind the shop. Purchases as a distribution source. # Giveaways Source: https://docs.nudj.cx/admin-guide/rewards/sweepstakes-giveaways Entry-based prize draws: entry period, draw date, automatic or manual winner selection, and entrant tracking A **Giveaway** is a prize draw. Members collect **entries** during an entry period; at the draw, winners receive the reward. Some teams call these sweepstakes, but across Nudj the term is giveaways. A giveaway runs in three phases, and the reward studio mirrors them: 1. **Entry period** - members earn entries between *Entries Open* and *Entries Close*. 2. **The draw** - at the *draw date*, winners are selected automatically or by you. 3. **Redemption** - winners claim their prize before its redeem-by deadline. *** ## Creating a giveaway Pick **Giveaway Reward** in the [type picker](/admin-guide/rewards/reward-types). The wizard is the same six steps as a Regular reward, with two steps adapted: ### Dates Giveaway Dates step showing Prize Draw Entry Period and Drawing Winners * **Phase 1: Prize Draw Entry Period** - **Entries Open** and **Entries Close** date-times. * **Drawing Winners** - the **draw date and time**, plus whether winners are selected **automatically on draw date** or **manually by admin**. * **Phase 2: Redeeming the Reward** - the date **winners must redeem their prize by**. The scheduling rules the form enforces: the draw must be **on or after Entries Close**; a draw date is **required** before an entries reward can publish; the draw date **locks once members hold entries** (unless the reward is back in Draft); and the redeem-by deadline must be **at least 24 hours after the draw**. Picking Entries Close auto-fills a sensible draw date and redeem-by, which you can then adjust. ### Supply Giveaway Supply step showing Number of Winners and entry supply fields * **Number of Winners** - how many prizes exist (or **Unlimited Winners**). * **Max Wins Per User** - one member can win more than once unless you cap it. * **Total Entries Supply** and **Max Entries Per User** - cap the ticket pool overall and per member. *** ## How members earn entries * **Challenges** - attach the giveaway on a challenge's **Distribution** step; the amount you set there is entries per completion. Achievements distribute entries the same way. * **The shop** - list the giveaway in the [Reward Store](/admin-guide/rewards/shop-integration) with an entry price and members buy entries with points. Entries can only be collected while the entry period is open. Once **Entries Close** passes (or the draw has run), earning and buying entries is rejected. *** ## The draw **Automatic** giveaways draw themselves at the draw date. Selection is a **weighted random draw**: every entry is a ticket, so a member with 10 entries has ten times the chance of a member with 1. Winners are drawn until the prize supply is used up, respecting **Max Wins Per User**. Each giveaway draws **once**. **Manual** giveaways wait for you. After the draw date passes, open the reward's **Stats -> Users** tab, select the winning members, and click **Assign as Winners**. You must select exactly as many members as there are remaining prizes. Automatic and manual are set at creation on the Dates step and are mutually exclusive: an automatic giveaway can't be hand-picked, and a manual one never draws itself. There is also no way to remove or disqualify individual entries, so in a manual giveaway, handle suspect activity by simply not selecting those members. *** ## After the draw * **Winners** get the reward as an asset in their **wallet**, with its redemption code attached, and must redeem it before the Phase 2 deadline. * **Losers** keep their entries, which resolve to a "better luck next time" state. * **Nobody is emailed.** There is no winner notification; members discover the result in the app - the wallet feed and the widget's giveaway card show "You won!" or the losing state. Announce winners yourself with a [post](/admin-guide/posts/creating-posts) if you want the moment amplified. *** ## Tracking entrants The reward's **Stats -> Users** tab lists every entrant with their **Total Entries** and **Last Entry** (plus a **Winner Status** column once a manual giveaway is drawn). Two exports: * **Export Users CSV** - the full entrant list with entry counts and, once drawn, per-user ticket outcomes. * **Tracking template CSV** (manual giveaways) - the same list with empty contact/winner-rank columns for running your draw offline. Keep the export for compliance archiving if your jurisdiction requires it. *** ## Next steps The full studio walkthrough. What winners do after the draw. Sell entries for points. Deliver to a fixed list directly, no draw involved. # Admin FAQs Source: https://docs.nudj.cx/admin-guide/settings/admin-faqs Common questions and answers from experienced admins You probably have questions. Here are answers to the questions we hear most from admins like you. **Can't find your answer?** This is living documentation. If you have a question that should be here, [reach out to support](mailto:support@nudj.com) or ask in the community. ## Organization & Setup Yes! Go to **Settings > Basic Configuration > General Settings** and update it. Changes take effect immediately across all communities. As many as you want, limited only by your plan. Check your plan to see seat limits. Need more? Upgrade anytime. Not directly, but you can export content from one organization and import it into another using CSV exports. It's manual but works. It gets archived (you can restore it). Data is preserved for 30 days before permanent deletion. Always backup important data first. ## Teams & Permissions Yes! Set up team members with specific roles limited to certain communities. Admins can be community-specific or organization-wide. **Managers** create and manage communities, challenges, and rewards. **Editors** create content but don't manage communities or delete things. Think of editors as content creators, managers as community owners. Go to **Settings > Team Members**, find the person, click their menu, select "Deactivate" or "Remove". Their contributions stay; they just lose access. Yes! Invite them normally, then remove them when the project ends. Consider using the Analyst role if they only need to view data. ## Branding & Customization No—in fact, it helps! Search engines prefer your domain over a generic one. Plus, it looks more professional to users. Yes! Subdomains work great. Some admins use subdomains for different communities (team1.mycompany.com, team2.mycompany.com). We provide color standards and font recommendations in settings. Stick to accessible colors (good contrast) and readable fonts. Not directly through the UI, but if you need deep customization, contact support about white-label options. ## Multi-Language Nope! Start with essentials (org name, community intro), then expand. Untranslated content falls back to your primary language. Depends on your budget. For 2-3 languages, professional translation pays for itself in user satisfaction. For testing, automated translation is fine initially. No! You can archive languages anytime. Existing translations stay (for historical reference), but users can't select it. Use the language most of your users speak. If you're global, English is a safe default, but pick what makes sense for you. ## Content & Variables 10-20 tags is usually ideal. Too few and categories are too broad, too many and users get overwhelmed. Start with 10, expand if needed. Yes! Variables work anywhere you write text. Use `{user_name}`, `{team_name}`, etc., and they'll be personalized for each user. Go to **Tag Manager**, find the tag, click "Bulk Apply", and select which challenges/rewards to tag. Much faster than tagging individually. Our system tracks answers and you can see trends, but custom scoring requires integration with your systems. ## Integrations Yes. We use industry-standard encryption and secure APIs. Never hardcode credentials, always use API keys with minimal permissions. Your Nudj data stays. Data you sent to external tools depends on their retention policies. Check with them. Yes! Send notifications when challenges are completed, users join, or rewards are distributed. Set up in Custom Integrations. We retry failed webhooks up to 5 times over 24 hours. Check integration logs if they're not arriving. ## Troubleshooting Check diagnostics (Settings > Advanced > Diagnostics). Filter by dates/reduce results. Clearing browser cache often helps. Check: Is the challenge live (not draft)? Does the user have permission? Are there targeting rules excluding them? Review logs. Check SMTP settings and email logs in diagnostics. Is sender address verified? Try a test send. Contact support if still stuck. Deleted items go to archives for 30 days. Go to **Settings > Advanced > Archive Management**. After 30 days, it's permanent. ## Plans & Billing Plans vary. Check our pricing page or contact sales. Most start with basic features and scale up as you grow. Yes! Changes take effect immediately. Upgrading charges prorated, downgrading applies credit to future invoices. Many organizations do! Reach out to sales and mention your nonprofit status. They can discuss options. Usually all features for 14-30 days. Check during signup. No credit card required (usually). ## Best Practices Weekly minimum. Export settings, challenges, and rewards regularly. Critical data (user list) should be exported monthly. Absolutely! Create a "staging" community where you test challenges, integrations, and settings before launching them. Every community is different, but 1-2 new challenges per week keeps things fresh without overwhelming users. Adjust based on engagement. Check analytics! Completion rates, average time, user feedback comments. What gets high completion? Do more of that. ## Getting Help Email [support@nudj.com](mailto:support@nudj.com) or click the help icon in the app. Response time is usually a few hours. Yes! Check the community section. Other admins often have solved your exact problem. Most plans include setup help. Some include 1-on-1 onboarding. Ask during signup. Absolutely! Sales team loves demos. [Book one](mailto:sales@nudj.com). ## Still Have Questions? Reach out: * **Email**: [support@nudj.com](mailto:support@nudj.com) * **Community**: Check the forums * **Schedule demo**: [sales@nudj.com](mailto:sales@nudj.com) * **Status**: Check [status.nudj.com](https://status.nudj.com) if something seems broken We're here to help. # Advanced Settings Overview Source: https://docs.nudj.cx/admin-guide/settings/advanced-overview Advanced settings redesign overview — integrations, shopify, custom integrations, admin FAQs The **Advanced Features & Workflows** section of Organisation Settings holds the integrations and workflow-oriented settings that power cross-system behaviour. The section was redesigned in #1031 for clearer grouping and less sidebar clutter. ## What's here Connect a Shopify store to fire events, sync customers, and unlock the embed + voice popup flows. Webhooks, custom API-linked auth, and bespoke integrations. Troubleshooting and most-asked questions. *** ## When to look here Most day-to-day admin work happens in the other settings sub-groups (Basic Configuration, User Access, Customisation, Content & Data). **Advanced** is for: * Launching a new external integration. * Debugging an existing integration. * Answering "why is X happening" that doesn't seem tied to core features. *** ## Access All Advanced settings require **Admin** role or above. Managers see the nav but the pages wrap in a `` — they can view, not edit. Viewers don't see the nav. See [Roles & Permissions](/admin-guide/settings/rbac-and-roles). # Asset Library Source: https://docs.nudj.cx/admin-guide/settings/asset-library Reusable image library for challenges, rewards, posts — with compression and aspect-ratio support The **Asset Library** (#1661) stores your uploaded images in one place so you can reuse them across challenges, rewards, posts, and themes — no more re-uploading the same hero image five times. ## What's in it * Every image uploaded to the admin panel * Organised by tag and upload date * Searchable by filename * Supports common image formats (including WebP) * Recommended aspect ratios surfaced via the crop tool where applicable (#828) *** ## Uploading Upload from any image picker in the admin — challenges, rewards, posts, theme. New uploads land in the Asset Library automatically. ### Standard aspect ratios | Ratio | Default use | | - | - | | 1:1 square | Challenge / reward / achievement icons | | 10:4 banner | Community banner (#777) | | 16:9 landscape | Post hero, video thumbnail | | 4:5 portrait | Mobile-first feed content | Use the crop tool to fit recommended ratios at upload time and avoid distortion at render time. *** ## Image format handling Uploads are validated for supported file types and size limits, then stored as-is. Upload WebP yourself when you want the bandwidth savings — 30-50% smaller than PNG/JPG at equivalent quality. Automatic format conversion is not currently part of the upload pipeline. *** ## Upload reporting Image upload success / failure is tracked (#1504) — useful for diagnosing mysterious "my image doesn't show" bugs. Upload icon overflow (#2296) was also fixed so the drag-drop target stays clickable. *** ## Background images Backgrounds for challenges, rewards, games, and themes all go through the same uploader (#996) — consistent crop tool, consistent compression. *** ## Reuse patterns Upload a banner for a challenge series; reuse on each challenge in the series. Visual coherence without re-uploads. Upload your brand's logo, hero, and accent images once. Reference from both the theme and individual content. Upload a set of reward images (e.g. four tiered prize visuals). Reuse on tiered rewards, tiered achievements, tiered leaderboards. *** ## API * `POST /api/files/upload` — upload an image (multipart form field `file`) * `DELETE /api/files/upload?url=` — delete a previously uploaded image by its public blob URL (Admin+ only) Both routes are auth-gated via the admin session. Library listing/browsing is handled by the admin UI on top of Vercel Blob — there is no public "list assets" endpoint. # Branding & Appearance Source: https://docs.nudj.cx/admin-guide/settings/branding-theming Unified theme system with live preview iframe, fonts, colour tokens, per-step challenge-complete colour Nudj has a **unified theme system** (#1874) that powers both the admin shell and member-facing apps. Themes cascade: org-level defaults apply to every community, and individual communities override specific tokens. ## Org-level themes Org Settings -> Branding & Appearance. What's configurable: Primary, accent, destructive, success, warning, sidebar, secondary. **Hex/RGB picker** (#1425) with token support. Kaufland (#796), Gotham (#938), Real Head Pro (#1464), or system fonts. Plus per-element font size (#911). Configurable widget loading screen (#2355). Lock members to a specific theme (#779) if you don't want them to toggle light/dark. *** ## Live preview iframe The right-hand panel shows a **live preview iframe** (#2305) — your changes apply in real-time. The theme editor has accessibility improvements, undo, and iframe sync (#2358) for a polished experience. *** ## Default theme New orgs default to the **parallax / immersive** theme (#1781) — hardcoded dark mode is no longer the baseline. All themes use design tokens (#2158) rather than hardcoded values. Placeholder values were removed from the theme config (#2246) — unset colours fall through to sensible defaults instead of displaying as broken empty states. *** ## Community-level theme overrides For per-community branding (sub-brands, regional variants), see [Community-Level Theming](/admin-guide/settings/community-branding) — covers #1545 and #1434 in detail. *** ## Per-step challenge-complete colour Customise the colour of the "challenge complete" celebration per step (#2204) — useful for multi-stage challenges where each step has its own visual identity. *** ## Tab visibility The **tab visibility** setting is part of the community theme (#2353) — control which tabs members see in the bottom nav. *** ## Fonts Ship with three premium fonts: | Font | Style | Good for | | - | - | - | | Kaufland | Modern sans | Tech / lifestyle brands | | Gotham | Condensed sans | Heritage / editorial brands | | Real Head Pro | Display serif | Premium / luxury brands | Or use a system font stack. *** ## Theme tokens (developers) For developers building custom themes or integrations, every Nudj UI uses CSS variables from the token system (#2158). See the theme system reference in the repo `docs/theme-system/` directory for full token reference. *** ## Quick palette swap The fastest way to apply a new brand: 1. Pick 6 colours (primary, accent, destructive, success, warning, sidebar). 2. Paste hex codes into the colour pickers. 3. Upload your logo. 4. Pick a font. 5. Watch the preview iframe. 6. Save. Total time: 5 minutes. # Community-Level Theming Source: https://docs.nudj.cx/admin-guide/settings/community-branding Per-community theme overrides, live preview iframe, tab visibility, per-step challenge-complete colour Organisations can have multiple communities with **different brands** — a sportswear store with regional communities, a media company with audience-segment communities, etc. Each community can override the org theme with its own colours, fonts, logos, and UI settings. ## Where to find it Community Settings -> **Branding & Theme**. *** ## What you can override Primary, accent, destructive, success, warning, sidebar — all overridable per community. Per-community logo and community banner. Override the org's font with a community-specific choice. Which member-facing tabs show (#2353). Hide Games for communities without games, Shop for progression-only communities. The celebration screen colour (#2204) can differ per community. Customise the widget loader per community. *** ## Live preview iframe The **live preview iframe** (#2305) on the right shows your changes in real-time — no save-and-refresh cycle. The theme editor has accessibility improvements, undo, and iframe sync (#2358) so changes never feel laggy. *** ## Community description & theme flag Each community has a description and an "active theme" flag (#1404) — controls whether overrides apply (on) or the org theme cascades through (off). *** ## Community theming v2 \#1545 introduced **v2 community theming** — a more comprehensive override set. If you're migrating from v1 (legacy), the admin handles the transition; existing overrides continue to work. *** ## The unified theme system All of this runs on the **unified theme system** (#1874) — a single data model that covers: * Admin shell chrome (sidebar, top nav, buttons) * Member app (bottom nav, feed, wallet, profile) * Widget embeds * Email templates Change a colour once, it propagates everywhere. See [Branding & Appearance](/admin-guide/settings/branding-theming) for the org-level counterpart. *** ## Strategy Keep community themes **similar enough to feel like one family**. Cross-community navigation should feel like a coherent brand, not jarring gear-shifts. Use **tab visibility overrides** sparingly. If you hide the Shop for three communities, maybe rethink whether those communities need Shop support at all (vs. a permanent org-level feature toggle). Overriding too many tokens leads to visual drift and inconsistency. The cleanest pattern: override **primary colour and logo** for brand identity, inherit everything else. # Content Templates Source: https://docs.nudj.cx/admin-guide/settings/content-templates Platform-level content templates, admin-only defaults, super-admin gated templates, copy-an-item flow Templates are pre-built challenges, rewards, achievements, and posts you can drop into a community to skip from-scratch creation. ## Template types Shipped with Nudj — curated examples for common use cases (onboarding, weekly engagement, product launch, etc.). Your team's reusable patterns. Admin-only (#2416) by default. Copy-an-item (#1412) from any existing content — clones the whole thing minus date fields. *** ## Template UI in the admin panel The template management UI moved into the admin panel (#888) — no more flipping between admin and a separate tool. Access via **Settings -> Content Templates**. *** ## Super-admin gated templates Some platform-level templates are **super-admin only** (#926) — they represent advanced patterns (e.g. a custom integration-driven challenge) that require careful setup. *** ## Admin-only default templates Templates shipped as "default on new community" (#2416) are locked to admin-role editing. This prevents accidental edits to your team's baseline patterns. *** ## Copy-an-item (related) The fastest way to create a "from template" content is [Copy / Duplicate Items](/admin-guide/settings/duplicate-and-copy) (#1412): 1. Open the source item. 2. Row menu -> Duplicate. 3. Rename + pick new schedule (date fields are excluded from the copy). *** ## Admin API Template management is exposed via the [Admin API](/api-reference/admin) — `/admin/content-templates` endpoints. Useful for programmatic rollouts across many communities. # Currency Labels Source: https://docs.nudj.cx/admin-guide/settings/currency-labels Rename the points currency shown in the embeddable loyalty widget. Use currency labels when a community needs its own member-facing name for points, such as "stars", "credits", or a branded programme currency. The canonical community field is `widgetLanguageConfig.points`: ```json theme={null} { "widgetLanguageConfig": { "points": { "name": "credit", "namePlural": "credits", "abbreviation": "CR" } } } ``` `name` is the singular label, `namePlural` is the plural label, and `abbreviation` is the compact label used in space-constrained widget surfaces. The canonical Zod schema is `WidgetLanguageConfigSchema` in [`packages/models/dto/community/model/widget-language-config-dto.model.ts`](https://github.com/Nudj-digital/monorepo/blob/main/packages/models/dto/community/model/widget-language-config-dto.model.ts). ## Worked example: credits A community can rename the default "points" currency to "credit" / "credits" with the abbreviation "CR". Use `communities.update` with the community's MongoDB ObjectId: ```json theme={null} { "operation": "update", "communityId": "685e76e02edaae2c4afa1db1", "widgetLanguageConfig": { "points": { "name": "credit", "namePlural": "credits", "abbreviation": "CR" } }, "fields": ["widgetLanguageConfig"] } ``` The same payload shape is accepted by the [Update Community Admin API](/api-reference/admin/post-communities-id) under the `widgetLanguageConfig` request field. ## Relationship to panel settings The MCP `panel` tool exposes `pointsName`, `pointsNamePlural`, and `pointsAbbreviation` as convenient widget-label fields alongside visual point styling such as colour and icon. For durable language configuration, prefer `communities.update` with `widgetLanguageConfig.points`. The widget applies `widgetLanguageConfig` text overrides after theme widget defaults, so the community-level language config wins if both are present. ## Verify the saved labels Read the community back with `communities.get` and request the field explicitly: ```json theme={null} { "operation": "get", "communityId": "685e76e02edaae2c4afa1db1", "fields": ["widgetLanguageConfig"] } ``` You should see the stored labels under `widgetLanguageConfig.points`. # Custom Integrations Source: https://docs.nudj.cx/admin-guide/settings/custom-integrations Connect external tools and services to your Nudj platform Your platform doesn't exist in isolation. You probably use Slack, email tools, CRMs, analytics platforms, and more. Custom integrations let you connect these tools so data flows smoothly between them. **Settings Location**: Navigate to **Control Room > Settings > Advanced Features & Workflows > Custom Integrations** to set up connections. ## What Can You Integrate? Anything with an API. Common integrations: Slack, Microsoft Teams, email services Salesforce, HubSpot, Pipedrive Google Analytics, Mixpanel, Amplitude AWS, Google Cloud, Dropbox Zapier, Make, n8n Your own internal systems ## How Integrations Work **Webhooks** — We send data to your tool when something happens * User completes challenge → Send data to your CRM * Reward claimed → Log in your analytics platform * New user joins → Add to your email list **API Connections** — We pull/push data to keep things in sync * Export challenge results to your spreadsheet * Update user profiles from your CRM * Fetch data from external systems to personalize challenges ## Setting Up an Integration Find it in our integrations marketplace or set up custom API From your tool's settings (usually an API key or OAuth token) Enter credentials or authorize connection in Nudj Tell us which Nudj events send to your tool and how Run a test, make sure data flows correctly, turn it on ## Webhooks: Pushing Data Out Whenever something happens in your platform, send data somewhere: * User joins community * User completes challenge * User earns reward * User reaches level milestone * Challenge created/updated * Reward distributed * Post published * Achievement unlocked * Settings changed * Team member added/removed * Integration toggled on/off Each event can trigger a webhook to send data to your external tool. ## API Connections: Syncing Data Pull or push data on a schedule: * **Sync user data** — Pull latest info from your CRM, update profiles * **Export results** — Push challenge results to your analytics tool * **Import content** — Pull questions from your question bank, create challenges * **Two-way sync** — Keep data consistent between systems ## Security & Permissions Store securely, rotate regularly, use specific scopes when possible Safer than API keys, users authorize connections directly Restrict API calls to specific IP addresses See all integration activity for compliance **Never** share API keys, commit them to GitHub, or put them in emails. Store them in a secure password manager. ## Monitoring Integrations Track integration health: Is the integration currently connected? When was the last successful sync or webhook? What went wrong and when? How much data are we syncing? Fix errors quickly—broken integrations mean data gets out of sync. ## Common Integration Patterns When something important happens (challenge completed, new user joins), send a notification to Slack. Keep your team in the loop without them logging in. When a user completes certain challenges, add them to a CRM segment. Track which users are most engaged in your business system. Send all user activity to your analytics tool. Analyze engagement trends, compare with other product activity, identify patterns. When users join or complete challenges, add them to relevant email lists. Automate follow-up campaigns. ## Best Practices **Go slow at first** * Start with one simple integration * Make sure it works before adding more * Don't integrate everything at once **Monitor actively** * Check integration health weekly * Set up alerts for failures * Fix broken integrations immediately **Keep credentials safe** * Use API keys with minimal permissions * Rotate keys regularly * Never hardcode credentials * Use environment variables **Document your setup** * Write down which integrations you have * Note what data flows where * Keep a list of who has API access ## Troubleshooting Integrations **"Connection failed"** * Check API credentials are correct * Verify the external tool hasn't changed their API * Check firewall/IP restrictions **"Webhooks not arriving"** * Verify webhook URL is correct * Check your receiving system's firewall * Test manually from our webhook tester **"Data looks wrong"** * Check field mappings (are we sending to the right fields?) * Verify data format (numbers vs. strings, dates formats) * Look at raw webhook data to see what we're actually sending **Pro tip**: Most integration problems are configuration issues, not bugs. Slow down, check settings carefully, and test each step. ## Next Steps 1. Identify what you want to integrate with 2. Get API credentials from that tool 3. Go to **Custom Integrations** 4. Set up your first integration (start simple) 5. Test it thoroughly before turning it on for real 6. Monitor integration health regularly Connected systems = better data flow = smarter decisions. # Custom Login Methods Source: https://docs.nudj.cx/admin-guide/settings/custom-login-methods Connect Nudj to your existing authentication system with OAuth or API Link You've got your own authentication system. We get it. Rather than making users log in twice, you can seamlessly pass them from your platform into Nudj. We support two approaches depending on your setup. **Settings Location**: Navigate to **Control Room > Settings > User Authentication** to configure custom login methods. ## Choose Your Path The method you choose depends on where your users are in their journey. **Best for: Apps, dashboards, and embedded experiences** Your users are already logged into your platform. They click a button, and boom—they're in Nudj. No login screen. No password re-entry. Just a seamless handoff. How it works: You generate a secure token on your backend and pass it to Nudj. We verify the token, recognize the user, and let them in instantly. **Setup complexity**: Medium (requires backend code) **User experience**: Instant, frictionless login **Best for: Direct web access, first-time visitors** Your users land at Nudj's login page without being authenticated yet. They see a "Log in with \[Your System]" button, click it, and are redirected to your login screen. Once they authenticate with you, we redirect them back to Nudj. How it works: Standard OAuth 2.0 / OpenID Connect (OIDC) flow. Your authentication system and Nudj coordinate to exchange credentials safely. **Setup complexity**: Medium (configuration in both systems) **User experience**: Familiar OAuth flow, industry standard ## Decision Matrix Not sure which one fits your use case? This should help: | Scenario | Use This | | - | - | | Users are in your app → you want to show Nudj inline | API Link | | Users are in your app → you want to open Nudj in a new tab | API Link | | Users navigate directly to Nudj's URL | OAuth | | You want the fewest steps for your users | API Link | | You're following industry-standard auth patterns | OAuth | | Your users need to authenticate to multiple services | OAuth | ## Implementation Details Both methods are secure and production-ready. The main difference is orchestration: who's coordinating the login flow? **API Link**: Your system is the coordinator. You hold the credentials and decide when/how to send users to Nudj. **OAuth**: Both systems coordinate together through standardized protocols. Step-by-step guide for generating secure JWT tokens and auto-signing users into Nudj. [View API Link Guide →](/developer/api-link-user-authentication) Configure your OAuth provider to work with Nudj's OpenID Connect flow. [View OAuth Guide →](/developer/oauth-authentication) ## Quick Setup Comparison 1. Get your `clientId` and `clientSecret` from Nudj 2. Generate a JWT token on your backend with user details 3. Build a URL: `https://your-domain/api/link?userToken=&clientId=` 4. Redirect your user to that URL 5. User lands in Nudj, fully authenticated [Full details →](/developer/api-link-user-authentication) 1. Set up OIDC configuration in Nudj (provide issuer, clientId, clientSecret) 2. Nudj generates a callback URL 3. Add that callback URL to your OAuth provider's whitelist 4. User clicks "Log in" at Nudj 5. User authenticates with your provider 6. User is redirected back to Nudj, fully authenticated [Full details →](/developer/oauth-authentication) **Not sure which to pick?** If you're embedding Nudj inside your app or redirecting from one of your pages, go API Link. If Nudj is accessed directly and you want a standard auth flow, go OAuth. **Both methods are secure**: API Link uses signed JWT tokens. OAuth uses industry-standard token exchange. Neither exposes user credentials unnecessarily. # Data Management Source: https://docs.nudj.cx/admin-guide/settings/data-management Data exports, retention, cross-tab with enterprise customer-data hydration Data management settings give you the levers to export community data, set retention policies, and control cross-app data flows. ## Data exports Nudj ships full CSV exports for every major entity: * **Challenges** — participants, per-action submissions, completion stats (#1241, #1243). * **Rewards** — allocations, redemptions, recipients. * **Giveaways** — entrant lists (#1133). * **Users** — member rows with stats. * **Posts** — engagement metrics. Exports stream from the Analytics API and contain the same member and analytics fields available in the UI. Review exported columns for personal data before sharing outside your organisation. *** ## Data retention Org-level retention settings control how long historical event data is kept: * Live analytics data is always available. * Aggregate analytics (completion rates, cohort retention) stays forever. * Raw event logs age out per retention policy. Changing retention shorter **permanently deletes** older raw events. Aggregates persist, but you lose the ability to drill into individual historical events past the retention window. *** ## Customer Data Hydration (cross-cutting) For enterprise customers, the **Customer Data Hydration** platform sync (detailed in the Enterprise tab) pushes member data to/from external systems (Shopify, Salesforce, custom CDPs). See the Enterprise documentation for the full pipeline. *** ## Bulk imports To seed members or content from an existing system: * Use the [bulk user actions](/admin-guide/user-management/bulk-actions) UI for members. * Use [content templates](/admin-guide/settings/content-templates) + copy-an-item for content. * Use the [Admin API](/api-reference/admin) for everything else. *** ## GDPR requests To satisfy a GDPR erasure request: 1. Find the member (Users -> search by email or ID). 2. **Delete user** — permanent removal of profile, balance, wallet. 3. Export a log of the deletion (timestamp, performer) from the audit log. For data-portability requests, export the member's full record via the [Admin API](/api-reference/admin). # Domains Source: https://docs.nudj.cx/admin-guide/settings/domains Attach custom member-facing domains to your org; onboarding flow Nudj lets you serve your member-facing community under your own domain (e.g. `community.yourbrand.com` instead of `community.nudj.cx`). ## Adding a domain Org Settings -> Domains -> Add. Enter your domain (e.g. `community.yourbrand.com`). Nudj provides a CNAME record to add to your DNS. Once the record propagates, the domain is verified. Once verified, Nudj provisions an SSL certificate automatically. Set the domain as Active. Members can now access your community at the new URL. *** ## Domain flow in onboarding During org onboarding (#728), domain settings are available upfront — you can wire your domain before your community even launches. Domain UX was cleaned up in #1312. *** ## Multiple domains per org You can attach multiple domains to a single org (one per community, or several pointing at the same community for marketing splits). The UI shows verification and SSL progress during setup for each domain. *** ## API access See the **Domains endpoints** in the [Admin API reference](/api-reference/admin) for add/get/remove domain operations. The API returns the current domain list as strings; per-domain verification and SSL state are reflected in the admin UI during setup rather than exposed as persistent programmatic status queries. *** ## Troubleshooting CNAME changes can take up to 48 hours to propagate globally. Re-verify after 24 hours before escalating. After verification, SSL provisioning takes 1-5 minutes. If it's still pending after 15 minutes, contact support. # Copy / Duplicate Items Source: https://docs.nudj.cx/admin-guide/settings/duplicate-and-copy Duplicate challenges, rewards, achievements, and more — with date-field exclusion Nudj's **Copy-an-Item** action (#1412) lets you duplicate any challenge, reward, achievement, or post in one click. It's the fastest way to reuse a proven pattern. ## How it works On any item's row menu (or edit page) click **Duplicate**. All content fields, actions, rewards, tags, language translations — everything copies over. Start / end dates are **not** copied from challenge templates (#2142) — you get a blank schedule, forcing you to pick a fresh window. New item gets a "(Copy)" suffix by default. Rename before saving. Copy starts as **Draft** regardless of the source's status. You opt-in to Live. *** ## What copies, what doesn't | Field | Copies? | | - | - | | Title, description, image | Yes | | Actions (for challenges) | Yes | | Reward / distribution attach | Yes | | Tags | Yes | | Translations | Yes | | Status | No (starts as Draft) | | Start / end dates | No (#2142) | | Participants / earned-by | No (fresh list) | | Analytics history | No | *** ## Use cases Ran a successful Black Friday challenge last year? Duplicate, tweak copy, pick new dates. Ready in 5 minutes. Duplicate a live challenge, change one variable (reward value, action count), launch both to compare. Duplicate a challenge from one community's perspective, change its community scope. Keep "template" challenges in Draft, duplicate them as starting points for new launches. Combine with [Content Templates](/admin-guide/settings/content-templates). *** ## Best practices **Always update the schedule** after duplicating. The date exclusion protects you, but you still need to pick fresh dates. **Review every translation** after copying. Auto-copied translations may not fit the new context (e.g. season references, time-sensitive copy). # Email Configuration Source: https://docs.nudj.cx/admin-guide/settings/email-configuration Configure custom sender; default platform email fallback Every org can configure a custom email sender so transactional emails (invites, reward notifications, achievement unlocks) appear to come from your brand domain. If not configured, Nudj falls back to a platform default sender. ## Configuring a custom sender Organisation Settings -> General -> **Email Configuration** (#1571). * **From name** — e.g. "Your Brand". * **From email** — e.g. `community@yourbrand.com`. * **Reply-to** — optional override. Nudj provides SPF / DKIM records for your DNS. Add them to your domain's DNS config, then click Verify. All transactional emails now use your custom sender. *** ## Fallback behaviour Orgs without custom email setup automatically use the Nudj platform default (#1671). This means: * Members still receive emails reliably, even if you don't configure your own sender. * You can delay custom sender setup without blocking the rest of your launch. * Emails arrive from a Nudj domain until you're ready to brand. *** ## What emails use this | Email | Trigger | | - | - | | Welcome | New member joins | | Team invite | Admin invites a teammate | | Reward earned | Member earns a reward | | Reward expiring | Asset reward near expiry | | Achievement unlocked | Member unlocks an achievement | | Streak reminder | Member's streak at risk | | Giveaway winner | Member wins a drawn giveaway | *** ## DNS records Nudj gives you three records to add: * SPF (authorises Nudj to send on your behalf) * DKIM (cryptographic signing) * DMARC (policy enforcement — optional but recommended) Email deliverability improves significantly with all three configured. *** ## Best practices Use a **subdomain** (`community.yourbrand.com`) rather than your primary domain for email sending. Protects your primary domain's reputation if something goes wrong with community emails. **Test before wide rollout** — invite yourself to your org using the custom sender, verify deliverability, check that it passes Gmail/Outlook filters. # Embed & Widget Controls Source: https://docs.nudj.cx/admin-guide/settings/embed-and-widget Embed emulator, widget loading state, community-level widget toggle, launcher form Nudj can be embedded as an iframe widget on your own website, Shopify storefront, or any HTML page. The embed and widget settings let you tune loading behaviour, visibility, and launcher config. ## Embed emulator The **Embed Emulator** (#1937) lets you test how your community looks embedded in different contexts — Shopify admin, a generic ecommerce store, a blog — without leaving the admin panel. * Pick an embed context (Shopify / ecommerce / blog / generic). * See your community render inside. * Test hover, click, and auth flows. * Debug iframe permission issues (mic, clipboard). *** ## Widget loading state The widget loading screen is **configurable** (#2355) — the colour, spinner style, and transition animation. Matches your community theme (or override per community). *** ## Community-level widget toggle Each community has a **widget toggle** (#2285) — enable or disable the widget per community. Turn off for communities that don't use embed/widget, on for the ones that do. *** ## Embed launcher form The embed launcher has a form for configuring: * **Mode** — identity (your OAuth) or nudj-login (Nudj-managed auth). * **Button position** — bottom-right, bottom-left, inline. * **Button label / icon**. * **Auto-open** — open automatically on page load for VIP users. * **FAB (floating action button) config**. The dirty-state fix (#2259) means unsaved form changes don't silently discard when you switch tabs — you get a confirm-navigation prompt. *** ## Shopify embed (related) The Shopify embed is a specialised form of iframe embed with mic-permission workarounds (voice popup, see [Shopify Integration](/admin-guide/settings/shopify-integration)) and dedicated tools. *** ## Widget best practices **Don't auto-open** the widget for every visitor. Auto-open is for returning members / VIPs. Cold visitors find it aggressive. Match your **widget loading state** to your brand. Default white-spinner-on-white looks unbranded and cheap. Spend 5 minutes on this. **Test on mobile** — embeds render very differently on mobile (often full-screen takeover rather than a floating button). Use the embed emulator's mobile mode before shipping. # Feature Toggles Source: https://docs.nudj.cx/admin-guide/settings/feature-toggles Admin-controlled feature flags for posts, achievements, shop, leaderboards, and more Feature toggles let admins turn major features on/off per community without needing an engineer. Reserved for **whole-feature** switches — granular configuration lives in the individual feature settings. ## What you can toggle Feature toggles come from two schemas: **feature visibility** (turn a whole feature on/off) and **tab visibility** (show/hide the tab in the member app). ### Feature visibility Turn off the whole gamification surface (points/XP/levels/challenges rewards). Disable the points economy (#731). Hide leaderboards. Disable the posts feature (#731). Allow posts but disable commenting. Disable the referral feature. ### Tab visibility Show/hide the Discover tab. Show/hide the Challenges tab. Show/hide the Rewards tab. Show/hide the Achievements tab. Show/hide the Posts tab. Show/hide the Games tab. *** ## When to use feature toggles Toggle OFF features you genuinely won't use. An always-off feature in the member app clutters the UI and confuses support queries. Don't use feature toggles as a workaround for unfinished content. If a tab is empty because you haven't built content, *build the content* — don't hide the whole feature. *** ## Feature toggle UX The settings screen was redesigned (#2155, #1126, #1125) with clearer groupings and in-line descriptions so non-technical admins can confidently flip switches. *** ## Related: platform flags vs community feature toggles Nudj has two flag systems: | System | Who controls | Use | | - | - | - | | **Community feature toggles** (this page) | Community admin | Hide/show member-facing features | | Platform feature flags (internal) | Nudj engineering | Gradual rollout of new features | You only interact with the community toggles. Platform flags are engineering internals. # General Settings Source: https://docs.nudj.cx/admin-guide/settings/general Org-level basics: name, logo, timezone, landing page, email, org switcher, onboarding **Organisation Settings -> General** is where you configure the top-level org basics that apply across every community. ## What's here Organisation name (shown in the top nav) and logo (#1255). Default timezone for scheduling. Per-challenge overrides available. Hero banner for your public landing page (#823, #1146). Custom sender. Falls back to platform default (#1571, #1671). See [Email Configuration](/admin-guide/settings/email-configuration). *** ## Org switcher If your account belongs to multiple orgs, the top-nav org switcher (#2419, #2379, #1172, #1213) jumps between them. Cookies are isolated (#1416) so switching never leaks data. See [Navigation Overview](/admin-guide/navigation-overview) for the full nav tour. *** ## Org onboarding New orgs go through a simplified onboarding flow (#728). Domain settings and initial community creation are integrated into the first-run experience. See [Creating an Organisation](/admin-guide/creating-organization) and [Domains](/admin-guide/settings/domains). *** ## Landing page The landing page header (#823, #1146) controls the hero image / tagline members see when they land on your org's public URL before picking a community. Configurable per brand. *** ## Email settings Custom email sender is configured in the general settings with a platform-default fallback (#1571, #1671). See [Email Configuration](/admin-guide/settings/email-configuration). # Language Management Source: https://docs.nudj.cx/admin-guide/settings/language-management Translations, language switcher, content-language cookie, multi-line edits, root-level JSON, game and challenge language support Nudj is built for multi-language out of the box. Every piece of content — challenges, rewards, achievements, posts, games, leaderboards, variables — supports translations. ## The translation editor Community Settings -> Languages. The editor shows: * Every translation key in the community. * The English source string. * Per-locale translations. * Multi-line support (#589) for long copy blocks. * Root-level JSON edits (#2012) for advanced use cases. The editor loads English on open (#716) so you always see the baseline. *** ## Language switcher The admin has an in-app language switcher (#746, #1053) — flip between locales to see how members in that language experience content. Posts studio has a **dedicated sidebar language switcher** (#2226) for in-flow translation during content creation. *** ## Content-language cookie + API `x-language` header Nudj sets a **content-language cookie** (#1916) that controls the `x-language` header on every API request. This makes the backend language-aware without URL prefixes. Adjust the cookie from the **content-language setting on the translations screen** (#1043). *** ## Multi-language variables Question action variables (#1171) support translation — the prompt, options, and stored data can all be localised. *** ## Language key reference A **language key reference** (#579) lists every key used in the community with its location. Helpful when you're translating and want to see where a string appears. *** ## Adding a new language Community Settings -> Languages -> Add. Pick the locale. New-language translations (#1163) ship with sensible platform defaults — so members see a usable app even before you translate every key. Per-challenge, per-reward, per-achievement translation. Language switch bugs across the admin were fixed in #845, #868, #2137, #2247. Optional: mark a non-English language as community default. *** ## Games support Games ship with **French and German defaults** (#2011) built-in. Other languages use the standard translation flow. *** ## Troubleshooting Older bug, fixed across #845, #868, #2137, #2247. Hard-refresh. If it persists, clear the Turbopack cache. Check the content-language cookie (#1916, #1043). It should match the community default. # Legal & Policies Source: https://docs.nudj.cx/admin-guide/settings/legal-policies Community rules, agreement checkboxes, permanent toggles, compliance Legal & Policies configures the rules and compliance surface for each community — what members must agree to before participating. ## Community rules Each community can have its own rich-text rules (#897): * Headings, lists, links, inline images. * Rendered in the member app on join and in a permanent footer (if enabled). * Translations supported per locale. * Empty / whitespace-only rules are sanitised (#2237). *** ## Agreement checkboxes Require members to tick an **"I agree to the rules"** checkbox before joining (#1099). Useful for: * Giveaways with legal/jurisdictional rules * Regulated industries (alcohol, gambling, pharma) * Communities with strict codes of conduct *** ## Permanent rules toggle Set the rules panel as **permanently visible** (#1435, #1361) — members can't dismiss it, and admins can't silently turn it off once enabled. This is a compliance safeguard for regulated verticals. Once the permanent toggle is on, it's persistent (#1361). Turning it off requires an admin action that's logged. Treat this as a "one-way door" for compliance reasons. *** ## Compliance page The compliance page (#1032) documents Nudj's own platform compliance posture — data residency, GDPR, cookie policies. Link to it from your own legal pages when required. *** ## Checklist for a compliance-critical launch Community rules written and published in every supported language. Agreement checkbox enabled on join. Permanent rules toggle considered (risk-based decision). Giveaway entry CSV export configured for audit retention. Nudj's own platform compliance page linked from your privacy policy. *** ## Next steps Configure SSO + social login. Branded sender, default fallback. # Navigation & Layout Source: https://docs.nudj.cx/admin-guide/settings/navigation-layout Mobile nav config, custom tabs editor, sidebar UX, drill-down pattern Configure how members navigate your community app — bottom tabs, drawer items, and the member-facing chrome. ## Member-facing navigation Members see a **bottom tab bar** in the member app: * Home / Feed * Challenges * Rewards * Leaderboards * Profile * (Optional: Shop, Games, Messages) Customise which tabs appear via the **custom tabs editor** (#865) in Community Settings -> Navigation & Layout. *** ## Mobile nav config Per-community mobile nav configuration (#831) lets you: * Hide / show tabs per community * Reorder tabs * Add custom links (e.g. direct to your storefront) *** ## Admin shell sidebar The admin panel uses a **drill-down sidebar** (#2216) with sections that expand inline — see [Navigation Overview](/admin-guide/navigation-overview). Settings sidebar spacing and UX was refined in #1119 and #1854 for a cleaner hierarchy. *** ## Community context in the nav bar The admin top nav always shows the current community (#1141). Switching community updates the entire admin context — sidebar items rescope, content reloads. *** ## Tab visibility via theme The **tab visibility** setting is part of the community theme (see [Community-Level Theming](/admin-guide/settings/community-branding)). Hide the Games tab for communities without games; hide Shop when you don't have a points economy. *** ## Best practices 5 tabs is the sweet spot. Members can scan all tabs at a glance. Going beyond 5 pushes tabs into an overflow menu that members miss. Put your biggest weekly value in the Home / Feed tab. Members open the app, see the feed, tap the first interesting thing. # Questions & Variables Source: https://docs.nudj.cx/admin-guide/settings/questions-variables Reusable question actions, multi-language variables, CSV import/export, per-user variables tab **Variables** are reusable question actions — write a question once, use it across many challenges. Member answers are stored on their profile as mapped variables and surface in analytics and integrations. ## Variable types Same shapes as question actions (see [Question Actions](/admin-guide/challenges/question-actions)): * Multiple choice (quiz or non-quiz) * Single choice * Free text * Rating / NPS * Select-value-in-range * Select-from-list * Secret code *** ## Creating a variable Settings -> Questions & Variables -> Add. Fill out the question, options (if applicable), and save. Available for use in any challenge's Content step (#974). *** ## Multi-language variables Variables support translations (#1171) — same translation editor as other content. Members see the variable in their chosen language. *** ## CSV import & export For bulk variable management (e.g. a large trivia bank or product lookup): * **Import** (#761) — upload a CSV of questions with options and answer keys. * **Export** (#756) — download your existing variables for audit or translation outsourcing. *** ## Per-user answers (Variables tab) On any member's profile, the **Variables / Questions tab** (#1578) shows every variable they've answered with their response. Useful for: * Customer support context * Personalisation ("you said you like X, here's a reward") * Segmentation for reward drops See [User Profiles](/admin-guide/user-management/user-profiles). *** ## Best practices Use variables for **questions you'll ask in many challenges** — e.g. "how satisfied with this purchase", "favourite product category". Per-challenge unique questions should be actions, not variables. Variables are perfect for **progressive profiling** — ask one new question per challenge and slowly build a full member profile over the first 10 interactions. # Roles & Permissions (RBAC) Source: https://docs.nudj.cx/admin-guide/settings/rbac-and-roles 5-role hierarchy, org vs community scope, per-user tokens, read-only Viewer patterns Nudj has a 5-role hierarchy, enforced at both client and server layers. Roles can be org-level (see every community) or community-scoped (see one specific community). ## The 5 roles | Level | Role | Short summary | | - | - | - | | 0 | **Viewer** | Read-only access to all subjects. Default for new invites. Good for analytics reviewers, external stakeholders. | | 1 | **Moderator** | Manage Comments, Posts, Users (moderation). Read-only on everything else. Good for community managers. | | 2 | **Manager** | Full CRUD on content (Challenges, Actions, Rewards, Posts, etc). Read-only on Users, Org, Config. Good for content creators. | | 3 | **Admin** | Full org management. Manage Users, Config, everything except delete Org. Good for admins. | | 4 | **SuperAdmin** | God mode. Full CRUD on all subjects. Rarely needed outside platform-level ops. | **Moderator** is new (#1865). The old **Creator** role was renamed to **Manager**, and **Analytics** was renamed to **Viewer**. *** ## Role scope Role strings encode scope: ```text theme={null} orgId-{id}:role-{role} ← org-level: sees every community orgId-{id}:comId-{id}:role-{role} ← community-scoped: sees one community only role-super-admin ← global SuperAdmin: bypasses all checks ``` Community scope limits what that member sees in dashboards, user lists, analytics. Org-level roles see everything. *** ## Permission matrix Permissions are `action + subject` pairs (`can-update:subject-Challenge`, etc). The 5 CRUD actions are: * `create`, `read`, `update`, `delete`, `manage` (= all four). Subjects: `Challenge, Action, Reward, Post, Achievement, User, Organisation, PlatformConfig, LeaderboardConfig, Team, AuthAccount, ...` Each role has an explicit allow/deny list. See `packages/models/util/rbac/config/role-permissions.config.ts` in the codebase for the source of truth. *** ## Per-user access tokens Each team member has a **personal access token** (#1816, #2352) for API calls and scripted automation. The token carries the member's roles in its JWT payload — the API derives permissions from there. ### Revoking tokens Admin+ can revoke any member's token via **Settings -> Team Members -> member -> Revoke Token**, or via the Admin API `/tokens/revoke` endpoint. Revocation is immediate. *** ## Client-side RBAC The admin UI hides controls the member can't use (#1864): * Buttons and menu items filter by permission. * Nav entries don't appear for unreadable subjects. * Edit pages wrap form content in `` — Viewers see the content but can't edit. This is **UX**, not security. Server-side enforcement is authoritative. *** ## Server-side enforcement Every API call validates the calling token's roles against the requested action+subject (#1875). Mismatched calls get a 403. Client-side restriction alone is insufficient — we enforce on both ends. *** ## Assigning roles Only **Admin+ (org-scoped)** or **global SuperAdmin** can assign roles. Members can assign roles **at or below their own level** — an Admin can assign Admin, Manager, Moderator, Viewer; they cannot assign SuperAdmin. Community-scoped Admin roles cannot assign roles at all — only org-level Admins can. See [Team Members](/admin-guide/settings/team-members) for the invite flow. *** ## ReadOnlyFieldset (the Viewer pattern) Edit pages use a `` wrapper. For Viewer and Moderator roles: * All form inputs disabled. * A "View only" banner appears at the top of the edit area. * Navigation stays enabled so Viewers can explore. * Save / delete / status-change buttons hide automatically. This pattern is applied to: * Challenge edit, Achievement edit, Post edit, Reward edit * Leaderboard edit, Game config edit, Streak edit * Organisation settings, Community settings *** ## Super-admin gated items Some platform-level items are **SuperAdmin only** (#926) — e.g. default templates that ship with new communities. Regular Admins can use them but can't edit the master templates. *** ## Invite-button permissions The **Invite** button on the Team Members page requires `update` permission on the Team subject (#1946). Moderators and below can't invite; Managers and above can. *** ## Custom roles? Nudj doesn't support custom roles (e.g. "CustomerSupport" with a bespoke permission set). The 5 built-in roles + org/community scope cover the vast majority of use cases. If you need more granularity, contact support. *** ## Best practices Default new team members to **Viewer**, then upgrade after you've confirmed their role. Easier than explaining why you removed permissions. For multi-community orgs, use **community-scoped roles** liberally — one community's manager shouldn't see another's private analytics. **SuperAdmin is rare**. Use it sparingly — typically only platform ops or the founding admin. Every SuperAdmin is a potential blast radius. # Shopify Integration Source: https://docs.nudj.cx/admin-guide/settings/shopify-integration Connect Nudj to Shopify: initiate endpoint, PostHog restore, embed voice popup Nudj has a first-class Shopify integration: fire events on purchase, sync customers, embed Nudj in the Shopify admin as an iframe, and run voice-enabled chat via a dedicated popup. ## Connecting Shopify Install the Nudj Shopify app from the Shopify App Store. Nudj exposes a **connection initiate endpoint** in the Admin API. Hit it from your Shopify app installer, Nudj returns the OAuth URL, finish the flow. Once connected, Shopify events (purchase, cart creation) arrive at Nudj. Verify in **Settings -> Integrations -> Shopify** status page. Create challenges with `purchase_completed` custom-event criteria (see [Designing Achievements](/admin-guide/achievements/designing-achievements)) to fire on Shopify purchases. *** ## Events fired Nudj receives these Shopify events by default: * `cart/create` * `cart/update` * `checkout/create` * `checkout/update` * `order/create` * `order/update` * `customer/create` * `customer/update` Each event fires a Nudj event (e.g. `purchase_completed`) that can power achievements, streaks, and reward distribution. *** ## Shopify embed + voice popup When Nudj is embedded inside the Shopify admin as an iframe, mic access is blocked by browser iframe permissions. The voice popup (#1936) opens a separate, non-sandboxed window with full mic permissions — voice transcripts flow back into the embed via `postMessage`. See [Nudj Agent - Voice mode](/admin-guide/ai-agent#voice-mode). *** ## Analytics restore (#1064) PostHog analytics restore for non-OneTrust deployments (#1064) — ensures member analytics fire correctly when your store doesn't use OneTrust cookie consent. *** ## Shopify app-level tools The Nudj Shopify app exposes a subset of admin features in the Shopify admin UI: * Quick-create challenges * Attach rewards to existing products / collections * View recent member activity * Voice-mode chat agent with full platform tools For embed settings (widget loading state, community toggle, launcher), see [Embed & Widget](/admin-guide/settings/embed-and-widget). *** ## Troubleshooting Check the Shopify webhook status in Shopify's admin. If they're failing, re-trigger via the Nudj connection status page. Browser popup blockers. See [Nudj Agent troubleshooting](/admin-guide/ai-agent#troubleshooting). # Standard Login Methods Source: https://docs.nudj.cx/admin-guide/settings/standard-login-methods Enable Google, Microsoft, Magic Link, YouTube, or Spotify for your members Give your members options. The more ways they can log in, the less friction they experience getting into your community. Pick the methods that make sense for your audience. **Settings Location**: Navigate to **Control Room > Settings > Standard Login Methods** to enable login providers. **Technical Level**: Basic setup required for each provider. ## How to Set Up Each Provider Setting up social login requires creating an "App" in the provider's developer console. This generates the **Client ID** and **Client Secret** you need to paste into Nudj. **Step 1: Create a Project** Go to the [Google Cloud Console](https://console.cloud.google.com/). Create a new project named "Nudj Community." **Step 2: Configure OAuth Screen** 1. Go to **APIs & Services > OAuth consent screen**. 2. Choose **External**. 3. Enter your app name and support email. **Step 3: Create Credentials** 1. Go to **Credentials > Create Credentials > OAuth client ID**. 2. Select **Web application**. 3. Add this **Authorized Redirect URI**: `https://{subdomain}.nudj.cx/api/auth/callback/google` 4. Copy your **Client ID** and **Client Secret**. **Step 1: Register an App** Go to the [Azure Portal](https://portal.azure.com/) and search for **App registrations**. Click **New registration**. **Step 2: Configure Redirect URI** 1. Name: "Nudj Community". 2. Supported account types: "Accounts in any organizational directory". 3. Redirect URI (Web): `https://{subdomain}.nudj.cx/api/auth/callback/microsoft-entra-id` **Step 3: Generate Secret** 1. Go to **Certificates & secrets > New client secret**. 2. Copy the **Value** (not the Secret ID). 3. From the **Overview** tab, copy your **Application (client) ID**. **No external setup needed!** Magic Links work out of the box. Users enter their email, and Nudj sends them a one-time login link. **Important**: Ensure your [Email Settings](/admin-guide/settings/email-configuration) are configured, or the emails won't be sent. ## Configuration Checklist Before you hit "Enable," make sure you have: * [ ] Created the App in the provider's console. * [ ] Added the correct **Redirect URI** (find yours in Nudj Settings). * [ ] Pasted the **Client ID** into Nudj. * [ ] Pasted the **Client Secret** into Nudj. **Pro tip**: Test each login method yourself before announcing them. Use an Incognito/Private window to sign in as a user and verify the flow. ## Need More Control? If you want to use your own proprietary authentication system, check out our custom methods. Learn how to use OAuth 2.0 or API Link tokens to sync users from your own app. # Tags Source: https://docs.nudj.cx/admin-guide/settings/tags Organize challenges, rewards, achievements, posts with tags — creation UI, reward-tag parity, human-readable names Tags let you organise content — challenges, rewards, achievements, posts — into groups for filtering, campaigns, and analytics. ## Creating tags Settings -> Tags -> **Create Tag** (#999). Each tag has: * **Name** — human-readable (#1950), shown in tables and member UI. * **Slug** — auto-derived from the name. * **Colour** — for visual grouping. * **Scope** — community-specific or org-wide. *** ## Applying tags Every content editor (challenges, rewards, achievements, posts) has a **Tags** field in the Basic Info step. Multi-select from existing tags, or type to create new. *** ## Tag UI parity Rewards previously had a separate ad-hoc tag system; #959 unified rewards with achievements / challenges / posts so they share the same tag picker and behaviour. *** ## Filtering by tag Every list view (challenges, rewards, etc.) supports filtering by one or more tags. Useful for: * Running a campaign: tag all campaign-related content with `campaign-name`. * Reporting: filter analytics by audience tag (`audience-b2b`, `audience-consumer`). * Content audits: find all tagged-as-legacy items to archive. *** ## Human-readable names Tables show the tag's human-readable name (#1950) rather than raw slugs. This is a small UX win that adds up on large content libraries. *** ## Best practices Stick to a **small taxonomy** (10-20 tags). Tag sprawl (100+ tags, each used once) makes filtering useless. Use **tag conventions**: prefix audience tags with `audience-`, campaign tags with `campaign-`, content-type tags with `type-`. Consistency pays off at scale. # Team Members Source: https://docs.nudj.cx/admin-guide/settings/team-members Invite collaborators, assign RBAC roles, manage per-user tokens Team members are the humans who have admin access to your org's Control Room. Each member has a role that controls what they can see and do. ## Roles 5-role hierarchy (see [Roles & Permissions](/admin-guide/settings/rbac-and-roles) for the full matrix): | Level | Role | Summary | | - | - | - | | 0 | **Viewer** | Read-only across every subject | | 1 | **Moderator** | Moderate comments, posts, users | | 2 | **Manager** | Full CRUD on content (challenges, rewards, posts, achievements) | | 3 | **Admin** | Full org management — users, config, everything except delete Org | | 4 | **SuperAdmin** | God mode — full CRUD on every subject | **Creator** and **Analytics** roles were renamed in #1865. If you see these in older references, they map to **Manager** (Creator) and **Viewer** (Analytics). *** ## Inviting a team member Organisation Settings -> Team Members. The Invite button is visible to Managers and above (#1946 — requires `update` permission on the Team subject). Email + role + org-wide or community-scoped. Picking a community limits that member to seeing that community's content. Nudj emails an invite link. The recipient signs in and is auto-assigned the role. *** ## Role scope Roles are either **org-level** (see everything) or **community-scoped** (see one specific community): ```text theme={null} orgId-{id}:role-{role} ← org-level orgId-{id}:comId-{id}:role-{role} ← community-scoped role-super-admin ← global superadmin (platform-wide) ``` Switch scope when editing a member's role. *** ## Per-user tokens Each member has a **personal access token** (#1816) used for admin API calls and scripted automation. Managers and above can view their own token; Admins can revoke any member's token. Token revocation signs the member out immediately everywhere. *** ## RBAC enforcement Roles are enforced at two layers: * **Client-side** (#1864) — UI hides buttons/fields the member can't use. Nav links don't show for subjects they can't read. * **Server-side** (#1875) — every API call validates the role against the subject + action. Client-side restriction is UX; server-side is security. See [Roles & Permissions](/admin-guide/settings/rbac-and-roles). *** ## Removing a team member Open their row in the Team Members list -> remove. Their access ends immediately (token revoked, session invalidated). Removing a member does **not** delete their content. Challenges, rewards, posts they created remain under their authorship — flag as orphaned if needed. *** ## Settings-page housekeeping Org settings were cleaned up / consolidated in #1312 — if you're on an older build, some old items may have moved. The current structure follows the [Customisation / User Access / Content & Data / Advanced](/admin-guide/settings/general) taxonomy. # Widget Section Theming Source: https://docs.nudj.cx/admin-guide/settings/widget-section-theming Per-section design controls — visibility, reorder, layouts, grid columns, image-first cards, and colour overrides — plus tabbed in-preview editors and editing sections from chat Your widget's body is built from **sections** — Earn & Spend, Achievements, Leaderboard, Daily Game, and more. Section theming lets you tune each one independently: hide or show it, reorder it, pick its layout, and override its colours — without touching the rest of the widget. The same section configuration is editable from two places, and both write to the same community theme: * **Settings** — Widget settings → **Body & Content** → **Section Ordering**. * **The live widget preview** — open a tabbed editor for any section straight from the preview, including inside the AI chat workspace. Every section option is available in both surfaces. A change you make in Settings shows up in the preview editor, and vice versa — there is one config behind both. ## The section catalogue Sections fall into two groups. Widget header, footer, the logged-out experience, and the Membership Tiers modal. Earn & Spend, Points for Spend, Achievements, Leaderboard, Daily Game, Daily Streak, Refer a Friend, Quick Actions, Receipt Scanning, and the standalone Earn and Spend sections. ## Per-section design controls Each section row in **Section Ordering** carries its own controls. ### Show or hide a section Every section has an **enable toggle** on the right of its row. Turn a section off to remove it from the widget body without deleting its configuration — flip it back on and your settings are still there. Two sections hide through their own control instead of the toggle: * **Leaderboard** — choose which live leaderboard to feature. Selecting **None (hidden)** hides the Leaderboard section on the storefront even while it is enabled, because there is nothing to show. * **Section header** — open a section's **Edit header** dialog (the pencil icon) to toggle **Show section header**, override the title and description, or **Clear custom header** to fall back to the defaults. For team members with the **Viewer** role, every section control is read-only — drag handles, toggles, and selects are disabled so Viewers can review the configuration without changing it. ### Reorder sections Drag the grip handle to reorder sections; the new order is saved with the section. Reordering is keyboard-accessible as well as pointer-driven. The **Earn** and **Spend** sections have a special behaviour: give both a non-compact card layout and they split into two independent sections. Return both to the default or Compact layout and they automatically **merge back** into a single **Earn & Spend** section, keeping the earlier position. ### Choose a layout per section Most sections expose a layout picker sized to that section. The choices, straight from the section editors: | Section | Layouts | | - | - | | Earn / Spend / Earn & Spend | Navigation, Compact, Promotional, Editorial (Promotional Two), Grid, Banner, Carousel | | Quick Actions | Compact, List, Carousel, Bubbles, Marquee, Feature | | Daily Game | Classic, Arcade, Compact, Hero, Grid, Minimal | | Refer a Friend | Classic, Spotlight, Stats, Split, Banner, Minimal | | Points for Spend | Classic, Spotlight, Compact, Split, Meter, Minimal | | Achievements | Classic, Showcase, Compact, Timeline, Spotlight, Badges | | Leaderboard | Classic, Podium, Compact, Ladder, Banner, Minimal | | Daily Streak | Classic, Calendar, Compact, Flame, Progress, Minimal | | Receipt Scanning | Classic, Spotlight, Compact, Steps, Ticket, Minimal | ### Grid columns and image-first cards When an Earn/Spend section uses the **Grid** card layout, the cards become **image-first** artwork tiles and a **Cards per row** control appears — pick **2**, **3**, or **4** columns. Grid and the other rich card layouts also unlock **featured items**: hand-pick which challenges or rewards appear, in what order, with per-item overrides: * **Image** — upload artwork or generate it with AI. Each layout shows its **recommended dimensions** (for example, Grid cards are 5:4 artwork) and feeds the right aspect ratio into the generator. * **Background** and **Text** colour overrides per item. * A **Description override**. * A **Max items** slider (1–5). ### Per-section colour and treatment overrides The **Style** dialog (the paintbrush trigger on a section row, and the **Style** tab in the preview editor) is where a section gets its own visual identity, independent of the global theme. It groups into: Header & background style — inherit the theme, or set a **Solid** colour, a **Gradient** (from/to), or a background **Image** URL — plus text alignment. Card style, border style, and — where the section has them — icon style and image style. Toggle individual elements per section: icon, description, metadata, progress, image, and CTA. Override the colours of the title, description, metadata, cards, borders, icons, and accent. Any override left empty inherits the theme colour. For sections with a call to action: the button treatment plus the CTA fill and text colours. Leave a colour override blank to inherit your community theme. Only set the ones you want to diverge — that way a brand-wide theme change still flows into every section you didn't explicitly pin. ## Tabbed in-preview section editors From the live widget preview you can open a focused editor for any single section. The editor splits its options into tabs so you work on one concern at a time: * **Content** — copy and what appears in the section. * **Layout** — the structural presentation (the layouts listed above). * **Style** — the treatment and colour overrides described above. Simpler surfaces show fewer tabs (the footer and logged-out experience are Content-only; Membership Tiers is Style-only), and single-tab editors drop the tab bar entirely. The editor drives the live preview as you type, and saving writes straight to the community theme. ### Unsaved-changes guards The section editors guard your work: * An **unsaved-changes dot** appears next to the section title while you have edits pending, and the browser warns before you navigate away. * Closing with unsaved edits prompts **Discard unsaved changes?** — discarding returns the live preview to the last saved version. * You can only edit **one section at a time**. Trying to open another section while one has unsaved edits prompts you to *save or discard your current changes first*. ## Editing sections from the live preview and chat Inside the AI chat workspace, the live widget preview is interactive. Each section in the preview can hand you straight into the right tool: * **Edit** a section — opens its tabbed editor. * **Move** a section up or down. * **Hide** a section — opens the editor with the section pre-set to hidden, still behind the unsaved-changes guard. * **Ask the agent** about a section — sends a section-scoped message into the chat composer so you can describe the change in words. Because the AI agent edits the same community theme behind these controls, you can mix approaches freely: ask the agent to restyle the Achievements section in chat, then fine-tune a colour by hand in the Style tab — both land on the same config and the same live preview. Use **Ask the agent** when you know the outcome you want but not which control to reach for ("make the Leaderboard feel more competitive"), and the hands-on Style tab when you want to nudge one exact value. ## Related * [Embed & Widget Controls](/admin-guide/settings/embed-and-widget) — loading state, community-level toggle, and the launcher form. * [Branding & Appearance](/admin-guide/settings/branding-theming) — the community-wide theme that section overrides inherit from. # Bulk User Actions Source: https://docs.nudj.cx/admin-guide/user-management/bulk-actions Delete users, adjust points, sync accounts, community scope, search by user ID For anything beyond single-member operations, use the bulk action flows on the Users page. Covered: bulk delete, bulk adjust, bulk add/remove from community, CSV sync. ## Where to find it **Admin -> Users -> select multiple rows -> actions menu.** *** ## Available bulk actions Permanent. Removes profile, balance, wallet (#1006). Use archive instead for soft-delete. Add or subtract balances across multiple members at once (#1006). Useful for campaign compensations. Invite many members at once to a new community (#696). Members must already exist at org level. Remove members from a specific community without deleting them (#696). CSV export of the selected rows — all fields visible in the table including rewards data (#1356). Gift the same reward to every selected member. For larger lists, use [Gift Rewards](/admin-guide/rewards/reward-gifting) with a CSV upload. *** ## Search patterns The users table supports search by: * Username (partial match) * Email (partial match) * **User ID** (#1294) — paste a MongoDB ObjectId to jump directly to that member. * Role or community (via filter drawer). *** ## Community scope Bulk actions respect community scope: * Admins see all members in the org. * Managers see members in their assigned communities. * Moderators can only perform bulk actions on moderation-relevant targets (e.g. archive). See [Roles & Permissions](/admin-guide/settings/rbac-and-roles). *** ## Synced accounts If your org has an external customer sync (e.g. Shopify), members display their **synced account** status in the table. Bulk-re-sync can be triggered from the row menu for rows with sync errors. See [Shopify Integration](/admin-guide/settings/shopify-integration). *** ## Safety Bulk delete is **permanent**. Use a test dataset first. The API does not support "bulk un-delete". Bulk adjust points can inflate or deflate your economy fast. Audit before applying. *** ## API For programmatic bulk operations, see the Admin API users endpoints: * `GET /admin/users` — list * `PATCH /admin/users/:id` — update * `DELETE /admin/users/:id` — delete * `POST /admin/users/:id/adjust-points` — adjust * `POST /admin/users/:id/gift-reward` — gift See the [Admin API reference](/api-reference/admin). # Users Overview Source: https://docs.nudj.cx/admin-guide/user-management/overview Manage members: search, bulk actions, per-user rewards, RBAC, community scope, variables tab The Users page lets admins search, view, and act on every community member. Capabilities depend on your RBAC role — Managers can adjust balances and gift rewards, Viewers see read-only. ## List & search * **Search** by username, email, or user ID (#1294). * **Filter** by status (verified, anonymous, archived), role, loyalty tier. * **Pagination** fixed in #1317 — page state persists across navigation. * **Rewards column** in the table (#1356) — at-a-glance view of how many rewards each member has claimed. *** ## Per-user actions Click any member row to open their profile. From there: ### Rewards tab Per-user **Rewards tab** (#704) shows: * All rewards earned or gifted. * Redemption status per reward. * Source (challenge, gift, drop). ### Mapped Variables tab The **Mapped Variables / Questions tab** (#1578) shows every question action the member has submitted with their raw answer. Useful for customer-support triage and personalisation. ### Adjust Points / XP For Managers and above: adjust the member's balance directly (#1006). See [Points & XP](/admin-guide/gamification/xp-currency). ### Delete user Permanent delete (#1006) — removes the member's profile, balance, and wallet. Not recoverable. Prefer **Archive** unless you need legal/compliance data removal. *** ## Bulk actions Select multiple members and: * **Add users** to a community (#696) * **Remove users** from a community (#696) * **Delete users** (Admin+ only) * **Bulk adjust** points/XP (#1006) * Export selected rows to CSV See [Bulk User Actions](/admin-guide/user-management/bulk-actions) for the full bulk operations guide. *** ## Community scope All user data is **community-scoped** (#1011) — a member in Community A and a member in Community B are separate records, even if they're the same human. Challenge completions, reward wallets, and variable answers don't cross community boundaries. Org-level admin roles (Admin, SuperAdmin) see **every** user across every community; community-scoped roles only see users in their assigned communities. *** ## RBAC Viewer, Moderator, Manager, Admin, SuperAdmin (see [Roles & Permissions](/admin-guide/settings/rbac-and-roles)): | Role | Read | Adjust points | Delete | Gift | Add/Remove | | - | - | - | - | - | - | | Viewer | Yes | No | No | No | No | | Moderator | Yes | No | No | No | No | | Manager | Yes | Yes | No | Yes | Yes | | Admin | Yes | Yes | Yes | Yes | Yes | | SuperAdmin | Yes | Yes | Yes | Yes | Yes | RBAC is enforced both client-side (buttons hidden) and server-side (API rejects) — see #1864, #1875, #1816. *** ## User profiles deep-dive Click any user's name or ID to open their full profile (#627). See [User Profiles](/admin-guide/user-management/user-profiles). # User Profiles Source: https://docs.nudj.cx/admin-guide/user-management/user-profiles Deep-dive on a single member: stats, activity, rewards, synced account, variables Every member has a profile page with a full cross-section of their activity. Click a username anywhere in the admin to land here. ## Tabs Stats — points earned, XP earned, challenges completed, current streak, level, loyalty tier. Chronological feed of actions taken and rewards earned. Wallet — earned vs. claimed vs. pending. Earned and in-progress achievements with progress bars. Question action responses (see [Mapped Variables](/admin-guide/user-management/overview#mapped-variables-tab)). External account sync status (Shopify, Salesforce, etc.) — surfaced via the Admin API. *** ## User stats page The dedicated **stats page** (#484) breaks out: * Total points earned vs. spent (net balance) * Total XP earned * Challenges / achievements / rewards counts * Streak length (current + longest) * Recent activity sparkline Use stats to understand "is this member engaged" before deciding on a gift or adjustment. *** ## Deep-link from any username Usernames anywhere in the admin (leaderboards, activity feed, giveaway entrants, challenge participants) link directly to the member's profile (#627). No need to search. *** ## Admin actions From the profile: * **Adjust points / XP** * **Gift a reward** * **Archive user** (soft delete) * **Delete user** (permanent, Admin+ only) * **Revoke access token** (logs member out everywhere) Role-based (see [Overview](/admin-guide/user-management/overview#rbac)). *** ## Synced accounts If your community has an integration (e.g. Shopify customer sync), the member's profile shows whether they're linked to an external account. Sync status errors surface here so you can triage integration issues. See [Integrations](/admin-guide/settings/shopify-integration) for platform-specific setup. # Achievement criteria completion Source: https://docs.nudj.cx/api-reference/achievement/achievement-criteria-completion /openapi/analytics.json get /achievements/stats/criteria-completion Completion rate and totals per criteria for an achievement # Achievement participants list Source: https://docs.nudj.cx/api-reference/achievement/achievement-participants-list /openapi/analytics.json get /achievements/stats/participants Paginated participants with summary stats # Achievement rewards overview Source: https://docs.nudj.cx/api-reference/achievement/achievement-rewards-overview /openapi/analytics.json get /achievements/stats/rewards Rewards distribution, XP/points timelines, assets # Achievement stats overview Source: https://docs.nudj.cx/api-reference/achievement/achievement-stats-overview /openapi/analytics.json get /achievements/stats/overview Returns overview stats and time series for an achievement # Assign a distribution details to an achievement Source: https://docs.nudj.cx/api-reference/achievement/assign-a-distribution-details-to-an-achievement /openapi/admin.json post /achievements/{achievementId}/distribution Assign a distribution details to an achievement. # Claim achievement rewards Source: https://docs.nudj.cx/api-reference/achievement/claim-achievement-rewards /openapi/integration.json post /achievements/{achievementId}/claim Claim rewards for a completed achievement. # Create a new achievement Source: https://docs.nudj.cx/api-reference/achievement/create-a-new-achievement /openapi/admin.json post /achievements Create a new achievement. # Get a distribution details for an achievement Source: https://docs.nudj.cx/api-reference/achievement/get-a-distribution-details-for-an-achievement /openapi/admin.json get /achievements/{achievementId}/distribution Get a distribution details for an achievement. # Get achievement by ID Source: https://docs.nudj.cx/api-reference/achievement/get-achievement-by-id /openapi/admin.json get /achievements/{achievementId} Retrieve an achievement by its ID. # Get paginated achievements Source: https://docs.nudj.cx/api-reference/achievement/get-paginated-achievements /openapi/admin.json get /achievements Retrieve a paginated list of achievements. # Update an achievement by id Source: https://docs.nudj.cx/api-reference/achievement/update-an-achievement-by-id /openapi/admin.json post /achievements/{id} Update an achievement by id. # Get action participation by ID Source: https://docs.nudj.cx/api-reference/action-participation/get-action-participation-by-id /openapi/admin.json get /action-participations/{actionParticipationId} Retrieve an action participation by its ID. # Get paginated action participations Source: https://docs.nudj.cx/api-reference/action-participation/get-paginated-action-participations /openapi/admin.json get /action-participations Retrieve a paginated list of action participations. # Assign a distribution details to an action Source: https://docs.nudj.cx/api-reference/action/assign-a-distribution-details-to-an-action /openapi/admin.json post /actions/{actionId}/distribution Assign a distribution details to an action. # Create a new action Source: https://docs.nudj.cx/api-reference/action/create-a-new-action /openapi/admin.json post /actions Create a new action. # Delete an action by id Source: https://docs.nudj.cx/api-reference/action/delete-an-action-by-id /openapi/admin.json delete /actions/{id} Delete an action by id. This is permanent and cannot be undone. # Get a distribution details for an action Source: https://docs.nudj.cx/api-reference/action/get-a-distribution-details-for-an-action /openapi/admin.json get /actions/{actionId}/distribution Get a distribution details for an action. # Get action by ID Source: https://docs.nudj.cx/api-reference/action/get-action-by-id /openapi/admin.json get /actions/{actionId} Retrieve an action by its ID. # Get paginated actions Source: https://docs.nudj.cx/api-reference/action/get-paginated-actions /openapi/admin.json get /actions Retrieve a paginated list of actions. # Reorder actions in a challenge Source: https://docs.nudj.cx/api-reference/action/reorder-actions-in-a-challenge /openapi/admin.json patch /actions/reorder Reorder the actions within a challenge by updating their positions. # Update an action by id Source: https://docs.nudj.cx/api-reference/action/update-an-action-by-id /openapi/admin.json post /actions/{id} Update an action by id. # Create action participation Source: https://docs.nudj.cx/api-reference/actionparticipation/create-action-participation /openapi/integration.json post /action-participations Create a new action participation. # Get action participation by id Source: https://docs.nudj.cx/api-reference/actionparticipation/get-action-participation-by-id /openapi/integration.json get /action-participations/{actionParticipationId} Retrieve an action participation by its ID. # Get paginated action participations Source: https://docs.nudj.cx/api-reference/actionparticipation/get-paginated-action-participations /openapi/integration.json get /action-participations Retrieve a paginated list of action participations. # Delete an action by id Source: https://docs.nudj.cx/api-reference/admin/delete-actions-id openapi/admin.json DELETE /actions/{id} Delete an action by id. This is permanent and cannot be undone. # Delete a game config by id Source: https://docs.nudj.cx/api-reference/admin/delete-game-configs-id openapi/admin.json DELETE /game-configs/{id} Delete a game configuration by id. This is permanent and cannot be undone. # Delete a tag by id Source: https://docs.nudj.cx/api-reference/admin/delete-tags-id openapi/admin.json DELETE /tags/{id} Delete a tag by id. This is permanent and cannot be undone. # Remove a user from an organisation Source: https://docs.nudj.cx/api-reference/admin/delete-teams-userId openapi/admin.json DELETE /teams/{userId} Remove a user from an organisation by removing their role. # Delete user personal data Source: https://docs.nudj.cx/api-reference/admin/delete-users-id openapi/admin.json DELETE /users/{id} Delete user personal data while keeping the user record for analytics. This anonymizes the user by removing personal information and replacing email/username with deleted user indicators. # Get paginated achievements Source: https://docs.nudj.cx/api-reference/admin/get-achievements openapi/admin.json GET /achievements Retrieve a paginated list of achievements. # Get achievement by ID Source: https://docs.nudj.cx/api-reference/admin/get-achievements-achievementId openapi/admin.json GET /achievements/{achievementId} Retrieve an achievement by its ID. # Get a distribution details for an achievement Source: https://docs.nudj.cx/api-reference/admin/get-achievements-achievementId-distribution openapi/admin.json GET /achievements/{achievementId}/distribution Get a distribution details for an achievement. # Get paginated action participations Source: https://docs.nudj.cx/api-reference/admin/get-action-participations openapi/admin.json GET /action-participations Retrieve a paginated list of action participations. # Get action participation by ID Source: https://docs.nudj.cx/api-reference/admin/get-action-participations-actionParticipationId openapi/admin.json GET /action-participations/{actionParticipationId} Retrieve an action participation by its ID. # Get paginated actions Source: https://docs.nudj.cx/api-reference/admin/get-actions openapi/admin.json GET /actions Retrieve a paginated list of actions. # Get action by ID Source: https://docs.nudj.cx/api-reference/admin/get-actions-actionId openapi/admin.json GET /actions/{actionId} Retrieve an action by its ID. # Get a distribution details for an action Source: https://docs.nudj.cx/api-reference/admin/get-actions-actionId-distribution openapi/admin.json GET /actions/{actionId}/distribution Get a distribution details for an action. # Get auth configuration Source: https://docs.nudj.cx/api-reference/admin/get-auth-config openapi/admin.json GET /auth-config Retrieve the authentication configuration settings. # Get paginated campaigns Source: https://docs.nudj.cx/api-reference/admin/get-campaigns openapi/admin.json GET /campaigns Get a paginated list of campaigns with optional filtering. # Get campaign by ID Source: https://docs.nudj.cx/api-reference/admin/get-campaigns-id openapi/admin.json GET /campaigns/{id} Get a campaign by its ID. # Get paginated challenge groups Source: https://docs.nudj.cx/api-reference/admin/get-challenge-groups openapi/admin.json GET /challenge-groups Retrieve a paginated list of challenge groups. # Get challenge group by ID Source: https://docs.nudj.cx/api-reference/admin/get-challenge-groups-id openapi/admin.json GET /challenge-groups/{id} Get a single challenge group by its ID # Get paginated challenges Source: https://docs.nudj.cx/api-reference/admin/get-challenges openapi/admin.json GET /challenges Retrieve a paginated list of challenges. # Get challenge by ID Source: https://docs.nudj.cx/api-reference/admin/get-challenges-challengeId openapi/admin.json GET /challenges/{challengeId} Retrieve a challenge by its ID. # Get a distribution details for a challenge Source: https://docs.nudj.cx/api-reference/admin/get-challenges-challengeId-distribution openapi/admin.json GET /challenges/{challengeId}/distribution Get a distribution details for a challenge. # Get paginated communities Source: https://docs.nudj.cx/api-reference/admin/get-communities openapi/admin.json GET /communities Retrieve a paginated list of communities. # Get community by ID Source: https://docs.nudj.cx/api-reference/admin/get-communities-communityId openapi/admin.json GET /communities/{communityId} Retrieve a community by its ID. # Get domain configuration Source: https://docs.nudj.cx/api-reference/admin/get-domains openapi/admin.json GET /domains Retrieve the domain configuration including API domain, user domain, and custom domains. # Get all feature flags Source: https://docs.nudj.cx/api-reference/admin/get-feature-flags openapi/admin.json GET /feature-flags Retrieve the status of all feature flags. # Get paginated game configs Source: https://docs.nudj.cx/api-reference/admin/get-game-configs openapi/admin.json GET /game-configs Retrieve a paginated list of game configurations. # Get game config by ID Source: https://docs.nudj.cx/api-reference/admin/get-game-configs-id openapi/admin.json GET /game-configs/{id} Retrieve a game configuration by its ID. # Get language configurations Source: https://docs.nudj.cx/api-reference/admin/get-languages openapi/admin.json GET /languages Retrieve all language configurations with full merged translations. Each language config contains the complete translation set (base locale merged with organisation-specific overrides). # Get aggregated leaderboard data Source: https://docs.nudj.cx/api-reference/admin/get-leaderboards-configId-data openapi/admin.json GET /leaderboards/{configId}/data Retrieve aggregated leaderboard data for a specific configuration. # Get paginated leaderboard configurations Source: https://docs.nudj.cx/api-reference/admin/get-leaderboards-configs openapi/admin.json GET /leaderboards/configs # Get leaderboard config by ID Source: https://docs.nudj.cx/api-reference/admin/get-leaderboards-configs-leaderboardConfigId openapi/admin.json GET /leaderboards/configs/{leaderboardConfigId} Retrieve a leaderboard config by its ID. # Get distribution details for a leaderboard config Source: https://docs.nudj.cx/api-reference/admin/get-leaderboards-leaderboardConfigId-distribution openapi/admin.json GET /leaderboards/{leaderboardConfigId}/distribution Get distribution details for a leaderboard config. # Get legal policies Source: https://docs.nudj.cx/api-reference/admin/get-legal-policies openapi/admin.json GET /legal-policies Retrieve the legal URLs and policies. # Get organisation by ID Source: https://docs.nudj.cx/api-reference/admin/get-organisations-organisationId openapi/admin.json GET /organisations/{organisationId} Retrieve an organisation by its ID. # Get paginated posts Source: https://docs.nudj.cx/api-reference/admin/get-posts openapi/admin.json GET /posts Retrieve a paginated list of posts. # Get post by id Source: https://docs.nudj.cx/api-reference/admin/get-posts-postId openapi/admin.json GET /posts/{postId} Retrieve a post by its ID. # Get paginated rewards Source: https://docs.nudj.cx/api-reference/admin/get-rewards openapi/admin.json GET /rewards Retrieve a paginated list of rewards. # Get reward by ID Source: https://docs.nudj.cx/api-reference/admin/get-rewards-rewardId openapi/admin.json GET /rewards/{rewardId} Retrieve a reward by its ID. # Get paginated reward entries for a specific reward Source: https://docs.nudj.cx/api-reference/admin/get-rewards-rewardId-entries openapi/admin.json GET /rewards/{rewardId}/entries Retrieves a paginated list of all values for a given variable configuration. # Get paginated reward redemption values for a specific reward Source: https://docs.nudj.cx/api-reference/admin/get-rewards-rewardId-redemption-values openapi/admin.json GET /rewards/{rewardId}/redemption-values Retrieves a paginated list of redemption values for a given reward. # Get paginated streak configs Source: https://docs.nudj.cx/api-reference/admin/get-streaks-configs openapi/admin.json GET /streaks/configs Retrieve a paginated list of streak configs. # Get streak config by ID Source: https://docs.nudj.cx/api-reference/admin/get-streaks-configs-id openapi/admin.json GET /streaks/configs/{id} Retrieve a streak config by its ID. # Get paginated tags Source: https://docs.nudj.cx/api-reference/admin/get-tags openapi/admin.json GET /tags Retrieve a paginated list of tags. # Get tag by ID Source: https://docs.nudj.cx/api-reference/admin/get-tags-id openapi/admin.json GET /tags/{id} Retrieve a specific tag by its ID. # Get paginated team members Source: https://docs.nudj.cx/api-reference/admin/get-teams openapi/admin.json GET /teams Retrieve a paginated list of team members for the organisation. # Get paginated achievement templates Source: https://docs.nudj.cx/api-reference/admin/get-templates-achievements openapi/admin.json GET /templates/achievements Get a paginated list of achievement templates available to your organisation. # Get paginated action templates Source: https://docs.nudj.cx/api-reference/admin/get-templates-actions openapi/admin.json GET /templates/actions Get a paginated list of action templates available to your organisation. # Get paginated challenge templates Source: https://docs.nudj.cx/api-reference/admin/get-templates-challenges openapi/admin.json GET /templates/challenges Get a paginated list of challenge templates available to your organisation. # Get paginated community templates Source: https://docs.nudj.cx/api-reference/admin/get-templates-communities openapi/admin.json GET /templates/communities Get a paginated list of community templates available to your organisation. # Get paginated post templates Source: https://docs.nudj.cx/api-reference/admin/get-templates-posts openapi/admin.json GET /templates/posts Get a paginated list of post templates available to your organisation. # Get paginated reward templates Source: https://docs.nudj.cx/api-reference/admin/get-templates-rewards openapi/admin.json GET /templates/rewards Get a paginated list of reward templates available to your organisation. # Get theme configuration Source: https://docs.nudj.cx/api-reference/admin/get-themes openapi/admin.json GET /themes Retrieve the theme and styling configuration. # Get third party configuration Source: https://docs.nudj.cx/api-reference/admin/get-third-party-config openapi/admin.json GET /third-party-config Retrieve the third party configuration settings. # Get paginated users Source: https://docs.nudj.cx/api-reference/admin/get-users openapi/admin.json GET /users Retrieve a paginated list of users. # Get user by ID Source: https://docs.nudj.cx/api-reference/admin/get-users-id openapi/admin.json GET /users/{id} Retrieve a user by their ID. # Get paginated variable configs Source: https://docs.nudj.cx/api-reference/admin/get-variables openapi/admin.json GET /variables Retrieve a paginated list of variable configs. # Get variable config by ID Source: https://docs.nudj.cx/api-reference/admin/get-variables-variableConfigId openapi/admin.json GET /variables/{variableConfigId} Retrieve a variable config by its ID. # Get paginated variable values for a specific variable config Source: https://docs.nudj.cx/api-reference/admin/get-variables-variableConfigId-values openapi/admin.json GET /variables/{variableConfigId}/values Retrieves a paginated list of all values for a given variable configuration. # Get webhook configurations Source: https://docs.nudj.cx/api-reference/admin/get-webhooks-configs openapi/admin.json GET /webhooks/configs Retrieve the webhook configurations for the organisation. # Get paginated webhook logs Source: https://docs.nudj.cx/api-reference/admin/get-webhooks-logs openapi/admin.json GET /webhooks/logs Retrieve a paginated list of webhook execution logs with optional filters. # Get XP configuration Source: https://docs.nudj.cx/api-reference/admin/get-xp-config openapi/admin.json GET /xp-config Retrieve the XP configuration settings. # Reorder actions in a challenge Source: https://docs.nudj.cx/api-reference/admin/patch-actions-reorder openapi/admin.json PATCH /actions/reorder Reorder the actions within a challenge by updating their positions. # Update auth configuration Source: https://docs.nudj.cx/api-reference/admin/patch-auth-config openapi/admin.json PATCH /auth-config Update the authentication configuration settings. # Update an existing campaign Source: https://docs.nudj.cx/api-reference/admin/patch-campaigns-id openapi/admin.json PATCH /campaigns/{id} Update an existing campaign. # Update a challenge group by id Source: https://docs.nudj.cx/api-reference/admin/patch-challenge-groups-id openapi/admin.json PATCH /challenge-groups/{id} Update a challenge group by id. # Update feature flags Source: https://docs.nudj.cx/api-reference/admin/patch-feature-flags openapi/admin.json PATCH /feature-flags Update the state of one or more feature flags. # Update a game config Source: https://docs.nudj.cx/api-reference/admin/patch-game-configs-id openapi/admin.json PATCH /game-configs/{id} Update an existing game configuration by id. # Update a leaderboard configuration Source: https://docs.nudj.cx/api-reference/admin/patch-leaderboards-configs-id openapi/admin.json PATCH /leaderboards/configs/{id} Updates an existing leaderboard configuration with the provided data # Update legal policies Source: https://docs.nudj.cx/api-reference/admin/patch-legal-policies openapi/admin.json PATCH /legal-policies Update the legal URLs and policies. # Update an organisation Source: https://docs.nudj.cx/api-reference/admin/patch-organisations-id openapi/admin.json PATCH /organisations/{id} Update an existing organisation. # Update a streak config by id Source: https://docs.nudj.cx/api-reference/admin/patch-streaks-configs-id openapi/admin.json PATCH /streaks/configs/{id} Update a streak config by id. # Update a tag by ID Source: https://docs.nudj.cx/api-reference/admin/patch-tags-id openapi/admin.json PATCH /tags/{id} Update specific fields of a tag by its ID. # Update theme configuration Source: https://docs.nudj.cx/api-reference/admin/patch-themes openapi/admin.json PATCH /themes Update the theme and styling configuration. # Update third party configuration Source: https://docs.nudj.cx/api-reference/admin/patch-third-party-config openapi/admin.json PATCH /third-party-config Update the third party configuration settings. # Update an existing variable config Source: https://docs.nudj.cx/api-reference/admin/patch-variables-id openapi/admin.json PATCH /variables/{id} Updates an existing variable configuration. # Update XP configuration Source: https://docs.nudj.cx/api-reference/admin/patch-xp-config openapi/admin.json PATCH /xp-config Update the XP configuration settings. # Create a new achievement Source: https://docs.nudj.cx/api-reference/admin/post-achievements openapi/admin.json POST /achievements Create a new achievement. # Assign a distribution details to an achievement Source: https://docs.nudj.cx/api-reference/admin/post-achievements-achievementId-distribution openapi/admin.json POST /achievements/{achievementId}/distribution Assign a distribution details to an achievement. # Update an achievement by id Source: https://docs.nudj.cx/api-reference/admin/post-achievements-id openapi/admin.json POST /achievements/{id} Update an achievement by id. # Create a new action Source: https://docs.nudj.cx/api-reference/admin/post-actions openapi/admin.json POST /actions Create a new action. # Assign a distribution details to an action Source: https://docs.nudj.cx/api-reference/admin/post-actions-actionId-distribution openapi/admin.json POST /actions/{actionId}/distribution Assign a distribution details to an action. # Update an action by id Source: https://docs.nudj.cx/api-reference/admin/post-actions-id openapi/admin.json POST /actions/{id} Update an action by id. # Revoke API token Source: https://docs.nudj.cx/api-reference/admin/post-auth-config-tokens-tokenId-revoke openapi/admin.json POST /auth-config/tokens/{tokenId}/revoke Revokes an API token by marking it as revoked in the platform configuration. This action is immediate and cannot be undone. # Create a new campaign Source: https://docs.nudj.cx/api-reference/admin/post-campaigns openapi/admin.json POST /campaigns Create a new campaign. # Create a new challenge group Source: https://docs.nudj.cx/api-reference/admin/post-challenge-groups openapi/admin.json POST /challenge-groups Create a new challenge group. # Create a new challenge Source: https://docs.nudj.cx/api-reference/admin/post-challenges openapi/admin.json POST /challenges Create a new challenge. # Assign a distribution details to a challenge Source: https://docs.nudj.cx/api-reference/admin/post-challenges-challengeId-distribution openapi/admin.json POST /challenges/{challengeId}/distribution Assign a distribution details to a challenge. # Update a challenge by id Source: https://docs.nudj.cx/api-reference/admin/post-challenges-id openapi/admin.json POST /challenges/{id} Update a challenge by id. # Create a new community Source: https://docs.nudj.cx/api-reference/admin/post-communities openapi/admin.json POST /communities Create a new community. # Update a community by id Source: https://docs.nudj.cx/api-reference/admin/post-communities-id openapi/admin.json POST /communities/{id} Update a community by id. # Add a domain Source: https://docs.nudj.cx/api-reference/admin/post-domains openapi/admin.json POST /domains Add a new custom domain to the platform configuration. # Remove a domain Source: https://docs.nudj.cx/api-reference/admin/post-domains-remove openapi/admin.json POST /domains/remove Remove a domain from the platform configuration. # Create a new custom event Source: https://docs.nudj.cx/api-reference/admin/post-events-custom openapi/admin.json POST /events/custom Create a new custom event. # Create a new event link Source: https://docs.nudj.cx/api-reference/admin/post-events-custom-link openapi/admin.json POST /events/custom/link Creates a link to the Nudj platform. When a user follows the link and registers/signs in, they will have the event logged on their behalf. # Create a game config Source: https://docs.nudj.cx/api-reference/admin/post-game-configs openapi/admin.json POST /game-configs Create a new game configuration. # Assign distribution details to a game config Source: https://docs.nudj.cx/api-reference/admin/post-games-gameConfigId-distribution openapi/admin.json POST /games/{gameConfigId}/distribution Assign multiple distribution details (prizes) to a game config. # Update language configuration Source: https://docs.nudj.cx/api-reference/admin/post-languages-language openapi/admin.json POST /languages/{language} Update or replace a language configuration. Omit translation to preserve existing overrides. Empty translation object means 'use default translations'. # Create a new leaderboard configuration Source: https://docs.nudj.cx/api-reference/admin/post-leaderboards openapi/admin.json POST /leaderboards Creates a leaderboard configuration that defines how user data should be aggregated # Create default leaderboard configs Source: https://docs.nudj.cx/api-reference/admin/post-leaderboards-default openapi/admin.json POST /leaderboards/default Create default leaderboard configs # Assign distribution details to a leaderboard config Source: https://docs.nudj.cx/api-reference/admin/post-leaderboards-leaderboardConfigId-distribution openapi/admin.json POST /leaderboards/{leaderboardConfigId}/distribution Assign distribution details to a leaderboard config. # Create a new organisation Source: https://docs.nudj.cx/api-reference/admin/post-organisations openapi/admin.json POST /organisations Create a new organisation. # Create a new post Source: https://docs.nudj.cx/api-reference/admin/post-posts openapi/admin.json POST /posts Create a new post. # Update a post by id Source: https://docs.nudj.cx/api-reference/admin/post-posts-id openapi/admin.json POST /posts/{id} Update a post by id. # Create a reward Source: https://docs.nudj.cx/api-reference/admin/post-rewards openapi/admin.json POST /rewards Create a new reward. # Update a reward by id Source: https://docs.nudj.cx/api-reference/admin/post-rewards-id openapi/admin.json POST /rewards/{id} Update a reward by id. # Process manual reward distribution Source: https://docs.nudj.cx/api-reference/admin/post-rewards-manual-distribution openapi/admin.json POST /rewards/manual-distribution Process a manual distirbution for a set of rewards to specific users. # Process manual reward giveaway Source: https://docs.nudj.cx/api-reference/admin/post-rewards-rewardId-process-giveaway openapi/admin.json POST /rewards/{rewardId}/process-giveaway Process a manual giveaway for a reward to specific users. Requires a list of user IDs and the quantity of rewards to distribute to each user. # Process reward redemption values from CSV Source: https://docs.nudj.cx/api-reference/admin/post-rewards-rewardId-redemption-values openapi/admin.json POST /rewards/{rewardId}/redemption-values Upload and process reward redemption values from a CSV file. The CSV file should have one redemption code per line, with no headers. # Create a new tag Source: https://docs.nudj.cx/api-reference/admin/post-tags openapi/admin.json POST /tags Create a new tag. # Invite users to an organisation Source: https://docs.nudj.cx/api-reference/admin/post-teams-invite openapi/admin.json POST /teams/invite Invite users to an organisation by email address. # Create a new entity from a template Source: https://docs.nudj.cx/api-reference/admin/post-templates-create-entity openapi/admin.json POST /templates/create-entity Create a new entity (community, challenge, achievement, action, post, or reward) from an existing template. # Generate Shopify linking code Source: https://docs.nudj.cx/api-reference/admin/post-third-party-config-initiate-shopify-connection openapi/admin.json POST /third-party-config/initiate-shopify-connection Creates or replaces linking code for Shopify account connection. Code expires in 15 minutes. # Create user Source: https://docs.nudj.cx/api-reference/admin/post-users openapi/admin.json POST /users Create a new user. # Manage points transaction Source: https://docs.nudj.cx/api-reference/admin/post-users-id-points-transaction openapi/admin.json POST /users/{id}/points-transaction Create a points transaction (credit or debit) for a user's account # Sync user to external account Source: https://docs.nudj.cx/api-reference/admin/post-users-sync-to-external-account openapi/admin.json POST /users/sync-to-external-account Sync a user to an external account. # Create a new variable config Source: https://docs.nudj.cx/api-reference/admin/post-variables openapi/admin.json POST /variables Creates a new variable configuration for collecting user data. # Update webhook configurations Source: https://docs.nudj.cx/api-reference/admin/put-webhooks-configs openapi/admin.json PUT /webhooks/configs Update the webhook configuration for the organisation. # Achievement criteria completion Source: https://docs.nudj.cx/api-reference/analytics/get-achievements-stats-criteria-completion openapi/analytics.json GET /achievements/stats/criteria-completion Completion rate and totals per criteria for an achievement # Achievement stats overview Source: https://docs.nudj.cx/api-reference/analytics/get-achievements-stats-overview openapi/analytics.json GET /achievements/stats/overview Returns overview stats and time series for an achievement # Achievement participants list Source: https://docs.nudj.cx/api-reference/analytics/get-achievements-stats-participants openapi/analytics.json GET /achievements/stats/participants Paginated participants with summary stats # Achievement rewards overview Source: https://docs.nudj.cx/api-reference/analytics/get-achievements-stats-rewards openapi/analytics.json GET /achievements/stats/rewards Rewards distribution, XP/points timelines, assets # Challenge action steps (funnel) Source: https://docs.nudj.cx/api-reference/analytics/get-challenges-stats-action-steps openapi/analytics.json GET /challenges/stats/action-steps Ordered challenge steps with participation breakdown. # Challenge actions list with summary Source: https://docs.nudj.cx/api-reference/analytics/get-challenges-stats-actions openapi/analytics.json GET /challenges/stats/actions Paginated action participations with summary for a challenge. # Challenge stats overview Source: https://docs.nudj.cx/api-reference/analytics/get-challenges-stats-overview openapi/analytics.json GET /challenges/stats/overview Aggregated challenge analytics: summary, actions breakdown, participantsByDay, timeseries, funnel. # Challenge users list with summary Source: https://docs.nudj.cx/api-reference/analytics/get-challenges-stats-users openapi/analytics.json GET /challenges/stats/users Paginated users who participated in a challenge, with summary stats. # Recent community activity Source: https://docs.nudj.cx/api-reference/analytics/get-community-activity-recent openapi/analytics.json GET /community/activity/recent Most recent events for the realtime feed. # Get community content overview (challenges + achievements) Source: https://docs.nudj.cx/api-reference/analytics/get-community-content-overview openapi/analytics.json GET /community/content/overview Aggregated overview of challenge and achievement metrics for a community over a date range. # Community activity time-series Source: https://docs.nudj.cx/api-reference/analytics/get-community-stats-activity-time-series openapi/analytics.json GET /community/stats/activity-time-series Daily counts and unique users by category. # Get community stats overview Source: https://docs.nudj.cx/api-reference/analytics/get-community-stats-overview openapi/analytics.json GET /community/stats/overview User-facing summary KPIs for a community over a date range (users, actions, challenges, rewards). # Community activity summary Source: https://docs.nudj.cx/api-reference/analytics/get-community-stats-summary openapi/analytics.json GET /community/stats/summary Totals and unique visitors for selected categories. # Community users list with summary stats Source: https://docs.nudj.cx/api-reference/analytics/get-community-users-list openapi/analytics.json GET /community/users/list Returns summary stats and a paginated list of users with community-computed stats. # Get reward allocations overview Source: https://docs.nudj.cx/api-reference/analytics/get-rewards-stats-allocations openapi/analytics.json GET /rewards/stats/allocations Get detailed allocation analytics for a specific reward including source information and distribution statistics # Rewards assets overview Source: https://docs.nudj.cx/api-reference/analytics/get-rewards-stats-assets-overview openapi/analytics.json GET /rewards/stats/assets/overview Get comprehensive analytics for reward assets including claims, redemptions, distribution sources, and time series data # Rewards assets redemption analytics Source: https://docs.nudj.cx/api-reference/analytics/get-rewards-stats-assets-redemptions openapi/analytics.json GET /rewards/stats/assets/redemptions Get detailed redemption analytics including redemption types, time to redeem distribution, and expiration analysis # Reward distribution events overview Source: https://docs.nudj.cx/api-reference/analytics/get-rewards-stats-distribution-overview openapi/analytics.json GET /rewards/stats/distribution/overview Get analytics for reward distribution events including success rates, event types, and performance metrics # Giveaways overview analytics Source: https://docs.nudj.cx/api-reference/analytics/get-rewards-stats-giveaways-overview openapi/analytics.json GET /rewards/stats/giveaways/overview Get comprehensive analytics for giveaways including participation, entry distribution, winners, and time series data # Giveaway participants list Source: https://docs.nudj.cx/api-reference/analytics/get-rewards-stats-giveaways-participants openapi/analytics.json GET /rewards/stats/giveaways/participants Get detailed list of giveaway participants with their entry counts, sources, and win/loss status # Get reward recipients Source: https://docs.nudj.cx/api-reference/analytics/get-rewards-stats-recipients openapi/analytics.json GET /rewards/stats/recipients Get list of users who received a specific reward with their quantities and dates # Get reward timeline data Source: https://docs.nudj.cx/api-reference/analytics/get-rewards-stats-timeline openapi/analytics.json GET /rewards/stats/timeline Get daily timeline data showing rewards earned and redeemed over time # User rewards analytics Source: https://docs.nudj.cx/api-reference/analytics/get-rewards-stats-users openapi/analytics.json GET /rewards/stats/users Get user-level reward analytics including portfolio, behavior patterns, and historical performance # List user activity (paginated) Source: https://docs.nudj.cx/api-reference/analytics/get-users-activity openapi/analytics.json GET /users/activity Returns a paginated list of user activities sorted by newest first. # Get comprehensive user details Source: https://docs.nudj.cx/api-reference/analytics/get-users-details openapi/analytics.json GET /users/details Retrieves comprehensive user details including stats, achievements, challenges, rewards, XP breakdown, and activity history. # Get users overview Source: https://docs.nudj.cx/api-reference/analytics/get-users-overview openapi/analytics.json GET /users/overview Retrieves a paginated overview of users # List user points transactions (paginated) Source: https://docs.nudj.cx/api-reference/analytics/get-users-points-transactions openapi/analytics.json GET /users/points-transactions Returns a paginated list of user points transactions sorted by newest first. # Get auth configuration Source: https://docs.nudj.cx/api-reference/auth-config/get-auth-configuration /openapi/admin.json get /auth-config Retrieve the authentication configuration settings. # Revoke API token Source: https://docs.nudj.cx/api-reference/auth-config/revoke-api-token /openapi/admin.json post /auth-config/tokens/{tokenId}/revoke Revokes an API token by marking it as revoked in the platform configuration. This action is immediate and cannot be undone. # Update auth configuration Source: https://docs.nudj.cx/api-reference/auth-config/update-auth-configuration /openapi/admin.json patch /auth-config Update the authentication configuration settings. # Authentication Source: https://docs.nudj.cx/api-reference/authentication How to authenticate with the Nudj Platform APIs The Nudj Platform APIs support two authentication header schemes. Both are verified by the **same** tRPC auth middleware (`apps/api/trpc/init.ts`) and both work on every endpoint — pick whichever suits your client. 1. **`Authorization: Bearer `** — the modern, revocable, RBAC-scoped path. Recommended for new integrations. 2. **`x-api-token: `** — the legacy alias. Still fully supported. The middleware strips an optional `Bearer ` prefix from either header before verifying the signature, so the two paths are equivalent at the signature layer. **Recommended for new integrations** RBAC-scoped, per-token revocation. Jump to [Authorization: Bearer](#authorization-bearer). **Legacy alias** Long-lived JWT. Jump to [x-api-token](#x-api-token). ## Authorization: Bearer Bearer tokens are the modern auth path introduced across NUDJ-4411 / NUDJ-4819 / NUDJ-5354. They carry a `type` claim, support per-community RBAC scoping, and are verified against a revocation cache on every request — revoking one takes effect within seconds. ### Get a Bearer token Go to [Settings → Organisation → Developer](https://admin.nudj.cx/admin/settings/organisation?tab=developer). Click **Generate New Token**. Pick a role (Viewer / Moderator / Manager / Admin / SuperAdmin) and optionally scope it to a specific community. Tokens are shown once. Store in a secrets manager or env var — never commit. ### Use in requests ```bash theme={null} Authorization: Bearer YOUR_API_TOKEN ``` The header is case-insensitive. Tokens are tied to the subdomain they were issued for — replaying a token against a different organisation's subdomain is rejected. ### Per-API support | API | `Authorization: Bearer` | `x-api-token` | Declared in OpenAPI spec | | - | - | - | - | | Integration | ✅ | ✅ | Both | | Admin | ✅ | ✅ | Both | | Analytics | ✅ | ✅ (runtime) | Bearer only | The Analytics OpenAPI spec currently declares only the Bearer scheme under `securitySchemes`. The runtime still accepts `x-api-token`, but the Analytics playground UI surfaces only the Bearer input. ## x-api-token ### Get a token Use the same Developer Settings page as above. Legacy tokens have no `type` claim and cannot be revoked individually — they remain valid until their expiry. ### Use in requests ```bash theme={null} x-api-token: YOUR_API_TOKEN ``` The header is case-insensitive. An optional `Bearer ` prefix on the value is accepted: ```bash theme={null} x-api-token: Bearer YOUR_API_TOKEN ``` ### Revoke a Bearer token Bearer tokens can be revoked individually via the Admin API: ```bash theme={null} curl -X POST "https://{subdomain}.nudj.cx/api/v2/admin/auth-config/tokens/{tokenId}/revoke" \ -H "Authorization: Bearer YOUR_ACTIVE_TOKEN" ``` ## API base URLs Each organisation has its own subdomain: ``` https://{your-subdomain}.nudj.cx/api/v2/{api-type} ``` Where `{api-type}` is one of `admin`, `integration`, or `analytics`. | Organisation | Subdomain | Admin API URL | | - | - | - | | Development | `nudjdev` | `https://nudjdev.nudj.cx/api/v2/admin` | | Staging | `nudjstaging` | `https://nudjstaging.nudj.cx/api/v2/admin` | | Your Org | `yourcompany` | `https://yourcompany.nudj.cx/api/v2/admin` | See [Organisation Domains](/api-reference/custom-domains) for white-label custom domains managed via the Admin API `/domains` endpoints. ## Testing authentication ```bash Admin API theme={null} curl -X GET "https://your-subdomain.nudj.cx/api/v2/admin/communities?limit=10" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" ``` ```bash Integration API theme={null} curl -X GET "https://your-subdomain.nudj.cx/api/v2/integration/me" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" ``` ```bash Analytics API theme={null} curl -X GET "https://your-subdomain.nudj.cx/api/v2/analytics/users/overview" \ -H "Authorization: Bearer YOUR_API_TOKEN" \ -H "Content-Type: application/json" ``` ## Error responses ### 401 Unauthorized ```json theme={null} { "message": "You must be logged in to access this endpoint.", "code": "UNAUTHORIZED", "data": { "httpStatus": 401, "path": "endpoint-name" } } ``` **Common causes:** * Missing or invalid token * Expired token * Token issued for a different organisation than the subdomain being requested * Incorrect header format ### 403 Forbidden ```json theme={null} { "message": "Insufficient permissions for this operation.", "code": "FORBIDDEN", "data": { "httpStatus": 403, "path": "endpoint-name" } } ``` **Common causes:** * Token lacks the required RBAC role * Token scoped to a different community than the one the endpoint targets * Endpoint requires a higher tier (admin vs moderator vs viewer) See [Errors](/api-reference/errors) for the full list of error shapes across every status code. ## Other headers you may need | Header | Purpose | See | | - | - | - | | `x-language` | Request translated response bodies in a specific locale | [Internationalization](/api-reference/internationalization) | | `x-user-access-token` | User-level auth on embed/widget flows | [API link user authentication](/developer/api-link-user-authentication) | | `x-admin-user-access-token` | Per-user RBAC when proxying through the admin app | [OAuth/SSO integration](/developer/oauth-authentication) | ## Security best practices Never expose API tokens in client-side JavaScript, public repositories, URL parameters, or browser storage. 1. **Store in secret managers** — use env vars or a secret store, never source control. 2. **Rotate regularly** — especially when team members change. 3. **Scope minimally** — use the least-privilege RBAC role the token actually needs. 4. **Revoke compromised Bearer tokens** — don't wait for them to expire. 5. **Use server-side requests only** — the APIs are not CORS-enabled for browser origins. ## Troubleshooting * Verify no extra whitespace or line breaks. * Confirm the token hasn't been revoked. * Ensure you're hitting the correct subdomain — tokens are org-scoped. * Verify the full path including `/api/v2/`. * Check the API type (admin vs integration vs analytics). * Confirm the endpoint exists in the API reference. * API runs on `https://localhost:3000` with a self-signed cert — use `--insecure` or `NODE_TLS_REJECT_UNAUTHORIZED=0`. * Confirm MongoDB is running and the org exists in the dev database. ## Need user-linked authentication? Configure OIDC so users can sign in via your existing auth provider. Auto-sign authenticated users into Nudj using JWT tokens. Access your API credentials and configuration. # Get my paginated badges Source: https://docs.nudj.cx/api-reference/badge/get-my-paginated-badges /openapi/integration.json get /me/badges Retrieve a paginated list of the current user's badges. # Create a new campaign Source: https://docs.nudj.cx/api-reference/campaign/create-a-new-campaign /openapi/admin.json post /campaigns Create a new campaign. # Get campaign by ID Source: https://docs.nudj.cx/api-reference/campaign/get-campaign-by-id /openapi/admin.json get /campaigns/{id} Get a campaign by its ID. # Get paginated campaigns Source: https://docs.nudj.cx/api-reference/campaign/get-paginated-campaigns /openapi/admin.json get /campaigns Get a paginated list of campaigns with optional filtering. # Update an existing campaign Source: https://docs.nudj.cx/api-reference/campaign/update-an-existing-campaign /openapi/admin.json patch /campaigns/{id} Update an existing campaign. # Create a new challenge group Source: https://docs.nudj.cx/api-reference/challenge-group/create-a-new-challenge-group /openapi/admin.json post /challenge-groups Create a new challenge group. # Get challenge group by ID Source: https://docs.nudj.cx/api-reference/challenge-group/get-challenge-group-by-id /openapi/admin.json get /challenge-groups/{id} Get a single challenge group by its ID # Get paginated challenge groups Source: https://docs.nudj.cx/api-reference/challenge-group/get-paginated-challenge-groups /openapi/admin.json get /challenge-groups Retrieve a paginated list of challenge groups. # Update a challenge group by id Source: https://docs.nudj.cx/api-reference/challenge-group/update-a-challenge-group-by-id /openapi/admin.json patch /challenge-groups/{id} Update a challenge group by id. # Assign a distribution details to a challenge Source: https://docs.nudj.cx/api-reference/challenge/assign-a-distribution-details-to-a-challenge /openapi/admin.json post /challenges/{challengeId}/distribution Assign a distribution details to a challenge. # Challenge action steps (funnel) Source: https://docs.nudj.cx/api-reference/challenge/challenge-action-steps-funnel /openapi/analytics.json get /challenges/stats/action-steps Ordered challenge steps with participation breakdown. # Challenge actions list with summary Source: https://docs.nudj.cx/api-reference/challenge/challenge-actions-list-with-summary /openapi/analytics.json get /challenges/stats/actions Paginated action participations with summary for a challenge. # Challenge stats overview Source: https://docs.nudj.cx/api-reference/challenge/challenge-stats-overview /openapi/analytics.json get /challenges/stats/overview Aggregated challenge analytics: summary, actions breakdown, participantsByDay, timeseries, funnel. # Challenge users list with summary Source: https://docs.nudj.cx/api-reference/challenge/challenge-users-list-with-summary /openapi/analytics.json get /challenges/stats/users Paginated users who participated in a challenge, with summary stats. # Complete challenge for user Source: https://docs.nudj.cx/api-reference/challenge/complete-challenge-for-user /openapi/integration.json post /challenges/{challengeId}/complete Validate and complete a challenge for user if all actions are done. Idempotent and safe against concurrent requests. # Create a new challenge Source: https://docs.nudj.cx/api-reference/challenge/create-a-new-challenge /openapi/admin.json post /challenges Create a new challenge. # Get a distribution details for a challenge Source: https://docs.nudj.cx/api-reference/challenge/get-a-distribution-details-for-a-challenge /openapi/admin.json get /challenges/{challengeId}/distribution Get a distribution details for a challenge. # Get challenge by ID Source: https://docs.nudj.cx/api-reference/challenge/get-challenge-by-id /openapi/admin.json get /challenges/{challengeId} Retrieve a challenge by its ID. # Get paginated challenges Source: https://docs.nudj.cx/api-reference/challenge/get-paginated-challenges /openapi/admin.json get /challenges Retrieve a paginated list of challenges. # Start challenge for user Source: https://docs.nudj.cx/api-reference/challenge/start-challenge-for-user /openapi/integration.json post /challenges/{challengeId}/start Start a challenge or new attempt for the authenticated user. Returns error if challenge is already in progress. # Unlock a challenge by spending keys Source: https://docs.nudj.cx/api-reference/challenge/unlock-a-challenge-by-spending-keys /openapi/integration.json post /challenges/{id}/unlock Spend keys to unlock a challenge. Returns the amount of keys spent and the new challenge status. # Update a challenge by id Source: https://docs.nudj.cx/api-reference/challenge/update-a-challenge-by-id /openapi/admin.json post /challenges/{id} Update a challenge by id. # Add a new comment Source: https://docs.nudj.cx/api-reference/comment/add-a-new-comment /openapi/integration.json post /comments Add a new comment to a discussion. # Add a new comment on a post Source: https://docs.nudj.cx/api-reference/comment/add-a-new-comment-on-a-post /openapi/integration.json post /posts/{postId}/comments Add a new comment on a post. # Update a comment Source: https://docs.nudj.cx/api-reference/comment/update-a-comment /openapi/integration.json put /comments/{commentId} Update an existing comment. # Update a comment on a post Source: https://docs.nudj.cx/api-reference/comment/update-a-comment-on-a-post /openapi/integration.json put /posts/{postId}/comments/{commentId} Update an existing comment on a post. # Community activity summary Source: https://docs.nudj.cx/api-reference/community/community-activity-summary /openapi/analytics.json get /community/stats/summary Totals and unique visitors for selected categories. # Community activity time-series Source: https://docs.nudj.cx/api-reference/community/community-activity-time-series /openapi/analytics.json get /community/stats/activity-time-series Daily counts and unique users by category. # Community users list with summary stats Source: https://docs.nudj.cx/api-reference/community/community-users-list-with-summary-stats /openapi/analytics.json get /community/users/list Returns summary stats and a paginated list of users with community-computed stats. # Create a new community Source: https://docs.nudj.cx/api-reference/community/create-a-new-community /openapi/admin.json post /communities Create a new community. # Get community by ID Source: https://docs.nudj.cx/api-reference/community/get-community-by-id /openapi/admin.json get /communities/{communityId} Retrieve a community by its ID. # Get community by ID or slug Source: https://docs.nudj.cx/api-reference/community/get-community-by-id-or-slug /openapi/integration.json get /communities/{identifier} Retrieve a community by its ID or slug. # Get community content overview (challenges + achievements) Source: https://docs.nudj.cx/api-reference/community/get-community-content-overview-challenges-+-achievements /openapi/analytics.json get /community/content/overview Aggregated overview of challenge and achievement metrics for a community over a date range. # Get community stats overview Source: https://docs.nudj.cx/api-reference/community/get-community-stats-overview /openapi/analytics.json get /community/stats/overview User-facing summary KPIs for a community over a date range (users, actions, challenges, rewards). # Get paginated communities Source: https://docs.nudj.cx/api-reference/community/get-paginated-communities /openapi/admin.json get /communities Retrieve a paginated list of communities. # Recent community activity Source: https://docs.nudj.cx/api-reference/community/recent-community-activity /openapi/analytics.json get /community/activity/recent Most recent events for the realtime feed. # Update a community by id Source: https://docs.nudj.cx/api-reference/community/update-a-community-by-id /openapi/admin.json post /communities/{id} Update a community by id. # Organisation Domains Source: https://docs.nudj.cx/api-reference/custom-domains Understand the domain patterns used by the Nudj APIs Every Nudj API endpoint is scoped to your organisation's domain. This page explains the patterns in use and points you at the Admin API endpoints that manage white-label custom domains. Looking to add a white-label domain for your members? The full admin walkthrough lives at [Custom Domain Setup](/admin-guide/settings/domains). ## Domain patterns Your organisation's API endpoints follow this pattern: ``` https://{your-organization-domain}/api/v2/{api-type}/{endpoint} ``` | Pattern | Example | When used | | - | - | - | | Standard Nudj subdomain | `acme-corp.nudj.cx` | Default for every organisation | | Custom subdomain | `api.yourcompany.com` | Registered via `POST /domains` | | White-label | `rewards.enterprise.com` | Registered via `POST /domains`, often paired with a member-facing brand domain | Tokens are tied to the subdomain they were issued for — a token minted for `acme-corp.nudj.cx` cannot be replayed against a different organisation's subdomain. This is enforced by the auth middleware at request time. ## Managing custom domains via the Admin API The Admin API exposes three endpoints for custom-domain lifecycle: List every domain registered for your organisation. Register a new custom domain. Triggers DNS + SSL provisioning. Retire a domain. Existing tokens scoped to that domain stop being accepted. For the DNS / SSL / verification walkthrough, see the [admin-side guide](/admin-guide/settings/domains) — that covers the UI flow in the admin dashboard that wraps these endpoints. ## Using your domain from code When building an integration, always use your organisation's actual domain — never a hard-coded dev/staging host. ```javascript JavaScript theme={null} const API_DOMAIN = process.env.NUDJ_API_DOMAIN; // e.g. 'acme-corp.nudj.cx' const API_TOKEN = process.env.NUDJ_API_TOKEN; async function fetchAchievements() { const response = await fetch( `https://${API_DOMAIN}/api/v2/integration/achievements`, { headers: { 'Authorization': `Bearer ${API_TOKEN}`, 'Content-Type': 'application/json' } } ); return response.json(); } ``` ```python Python theme={null} import os import requests API_DOMAIN = os.environ['NUDJ_API_DOMAIN'] # e.g. 'acme-corp.nudj.cx' API_TOKEN = os.environ['NUDJ_API_TOKEN'] def fetch_achievements(): response = requests.get( f'https://{API_DOMAIN}/api/v2/integration/achievements', headers={ 'Authorization': f'Bearer {API_TOKEN}', 'Content-Type': 'application/json' } ) return response.json() ``` ## Finding your domain 1. **Check Developer Settings** — [Settings → Organisation → Developer](https://admin.nudj.cx/admin/settings/organisation?tab=developer) lists the active domain for your organisation. 2. **Match your admin URL** — the admin dashboard for your organisation runs on the same subdomain (e.g. admin is served under `{your-subdomain}.nudj.cx`). 3. **Ask your administrator** — they have the full list of registered custom domains and can issue a new one via the Admin API or the admin UI. ## Troubleshooting * Verify you're hitting your organisation's actual domain — not `nudjdev` or `nudjstaging`. * Check the path starts with `/api/v2/` and uses `integration`, `admin`, or `analytics`. * Confirm the endpoint exists for the API type you're calling. * Your token is scoped to a specific subdomain. Using it against a different domain (including `nudjdev` from a production token) returns 401. * Re-issue the token from the correct organisation's Developer Settings if you need to test against another tenant. * The APIs are not CORS-enabled for browser origins. Proxy all requests through your own backend — do not call these endpoints directly from a browser. ## Related Token formats, API auth headers, and how requests are scoped to your domain. Full DNS and SSL walkthrough for registering a white-label domain. # Add a domain Source: https://docs.nudj.cx/api-reference/domains/add-a-domain /openapi/admin.json post /domains Add a new custom domain to the platform configuration. # Get domain configuration Source: https://docs.nudj.cx/api-reference/domains/get-domain-configuration /openapi/admin.json get /domains Retrieve the domain configuration including API domain, user domain, and custom domains. # Remove a domain Source: https://docs.nudj.cx/api-reference/domains/remove-a-domain /openapi/admin.json post /domains/remove Remove a domain from the platform configuration. # Errors Source: https://docs.nudj.cx/api-reference/errors The shared tRPC error envelope and common status codes Every Nudj API endpoint uses the same error envelope — the tRPC wire format — so clients can share a single error handler across all three APIs. ## Envelope ```json theme={null} { "message": "Human-readable description of the problem.", "code": "TRPC_ERROR_CODE", "data": { "httpStatus": 400, "path": "endpointName" } } ``` | Field | Description | | - | - | | `message` | Free-text description. Safe to surface in developer-facing logs. **Don't surface verbatim to end users** — messages aren't localised. | | `code` | Stable tRPC error code — e.g. `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BAD_REQUEST`, `INTERNAL_SERVER_ERROR`. | | `data.httpStatus` | The HTTP status code of the response. Mirrors the response status line. | | `data.path` | Name of the endpoint procedure that rejected the request. Useful for log correlation. | ## Common status codes ### 400 — `BAD_REQUEST` Zod validation failure on the request body, a path/query parameter, or a required header. ```json theme={null} { "message": "Invalid input: expected string for body.communityId", "code": "BAD_REQUEST", "data": { "httpStatus": 400, "path": "createChallenge" } } ``` **Fix:** read the `message` — it names the offending field. Re-check your request shape against the endpoint's schema. ### 401 — `UNAUTHORIZED` Token missing, malformed, expired, or issued for a different organisation's subdomain. ```json theme={null} { "message": "You must be logged in to access this endpoint.", "code": "UNAUTHORIZED", "data": { "httpStatus": 401, "path": "getAchievements" } } ``` **Fix:** see [Authentication troubleshooting](/api-reference/authentication#troubleshooting). ### 403 — `FORBIDDEN` Token is valid but lacks the RBAC permissions for the requested action. Most common on Admin endpoints. ```json theme={null} { "message": "Insufficient permissions for this operation.", "code": "FORBIDDEN", "data": { "httpStatus": 403, "path": "deleteChallenge" } } ``` **Fix:** raise the token's role (if appropriate), or narrow the request to a community the token has access to. ### 404 — `NOT_FOUND` No entity matches the request — wrong id, wrong organisation, or the entity has been deleted. ```json theme={null} { "message": "Challenge not found.", "code": "NOT_FOUND", "data": { "httpStatus": 404, "path": "getChallengeById" } } ``` ### 409 — `CONFLICT` The request would break a uniqueness constraint or violates current state (e.g. trying to claim a reward already claimed, re-use a one-time code, or register a duplicate domain). ```json theme={null} { "message": "Reward already claimed.", "code": "CONFLICT", "data": { "httpStatus": 409, "path": "claimReward" } } ``` ### 500 — `INTERNAL_SERVER_ERROR` An unexpected runtime error. The response body surfaces the tRPC `code` but the detailed stack trace is logged server-side and not returned. ```json theme={null} { "message": "An unexpected error occurred.", "code": "INTERNAL_SERVER_ERROR", "data": { "httpStatus": 500, "path": "getMyProfile" } } ``` **Response-validation errors can also surface as 500.** If the API returns data that fails its own output schema (historically this has happened on a few integration endpoints when new enum values are rolled out), clients receive a generic 500 rather than a typed failure. If you consistently hit a 500 on a previously-working endpoint, file a bug — it's very likely a schema regression rather than a runtime fault. ## Client handling pattern ```javascript JavaScript theme={null} async function nudjRequest(path, init = {}) { const response = await fetch(`https://${DOMAIN}${path}`, { ...init, headers: { 'Authorization': `Bearer ${process.env.NUDJ_API_TOKEN}`, 'Content-Type': 'application/json', ...(init.headers ?? {}), }, }); if (!response.ok) { const error = await response.json().catch(() => null); // error has shape { message, code, data: { httpStatus, path } } throw Object.assign( new Error(error?.message ?? `HTTP ${response.status}`), { code: error?.code, httpStatus: response.status, path: error?.data?.path }, ); } return response.json(); } ``` ```python Python theme={null} import os, requests def nudj_request(path, **kwargs): response = requests.request( url=f"https://{DOMAIN}{path}", headers={ "Authorization": f"Bearer {os.environ['NUDJ_API_TOKEN']}", "Content-Type": "application/json", **kwargs.pop("headers", {}), }, **kwargs, ) if not response.ok: try: error = response.json() except ValueError: error = {} raise RuntimeError( f"Nudj API error: {error.get('code', response.status_code)} — {error.get('message')}" ) return response.json() ``` ## Retries The APIs don't emit `Retry-After` headers, but 5xx responses (`500`, `502`, `503`) are always safe to retry with exponential backoff. Do **not** blindly retry `4xx` — those are request-shape errors and will fail identically a second time. ## Related 401 and 403 scenarios in depth. Common 400 errors on list endpoints. # Claim an event token Source: https://docs.nudj.cx/api-reference/event/claim-an-event-token /openapi/integration.json post /events/claim Claims an event token and logs the event for the user. # Create a new custom event Source: https://docs.nudj.cx/api-reference/event/create-a-new-custom-event /openapi/admin.json post /events/custom Create a new custom event. # Create a new event link Source: https://docs.nudj.cx/api-reference/event/create-a-new-event-link /openapi/admin.json post /events/custom/link Creates a link to the Nudj platform. When a user follows the link and registers/signs in, they will have the event logged on their behalf. # Get paginated events Source: https://docs.nudj.cx/api-reference/event/get-paginated-events /openapi/integration.json get /events Retrieve a paginated list of events. # Get all feature flags Source: https://docs.nudj.cx/api-reference/feature-flags/get-all-feature-flags /openapi/admin.json get /feature-flags Retrieve the status of all feature flags. # Update feature flags Source: https://docs.nudj.cx/api-reference/feature-flags/update-feature-flags /openapi/admin.json patch /feature-flags Update the state of one or more feature flags. # Assign distribution details to a game config Source: https://docs.nudj.cx/api-reference/game-config/assign-distribution-details-to-a-game-config /openapi/admin.json post /games/{gameConfigId}/distribution Assign multiple distribution details (prizes) to a game config. # Create a game config Source: https://docs.nudj.cx/api-reference/game-config/create-a-game-config /openapi/admin.json post /game-configs Create a new game configuration. # Delete a game config by id Source: https://docs.nudj.cx/api-reference/game-config/delete-a-game-config-by-id /openapi/admin.json delete /game-configs/{id} Delete a game configuration by id. This is permanent and cannot be undone. # Get game config by ID Source: https://docs.nudj.cx/api-reference/game-config/get-game-config-by-id /openapi/admin.json get /game-configs/{id} Retrieve a game configuration by its ID. # Get paginated game configs Source: https://docs.nudj.cx/api-reference/game-config/get-paginated-game-configs /openapi/admin.json get /game-configs Retrieve a paginated list of game configurations. # Update a game config Source: https://docs.nudj.cx/api-reference/game-config/update-a-game-config /openapi/admin.json patch /game-configs/{id} Update an existing game configuration by id. # Unlike a post Source: https://docs.nudj.cx/api-reference/integration/delete-posts-postId-like openapi/integration.json DELETE /posts/{postId}/like Unlike a post by its ID. # Get paginated achievements Source: https://docs.nudj.cx/api-reference/integration/get-achievements openapi/integration.json GET /achievements Retrieve a paginated list of achievements. # Get achievement by ID Source: https://docs.nudj.cx/api-reference/integration/get-achievements-achievementId openapi/integration.json GET /achievements/{achievementId} Retrieve an achievement by its ID. # Get paginated action participations Source: https://docs.nudj.cx/api-reference/integration/get-action-participations openapi/integration.json GET /action-participations Retrieve a paginated list of action participations. # Get action participation by id Source: https://docs.nudj.cx/api-reference/integration/get-action-participations-actionParticipationId openapi/integration.json GET /action-participations/{actionParticipationId} Retrieve an action participation by its ID. # Get paginated actions Source: https://docs.nudj.cx/api-reference/integration/get-actions openapi/integration.json GET /actions Retrieve a paginated list of actions. # Get action by ID Source: https://docs.nudj.cx/api-reference/integration/get-actions-actionId openapi/integration.json GET /actions/{actionId} Retrieve an action by its ID. # Get auth configuration Source: https://docs.nudj.cx/api-reference/integration/get-auth-config openapi/integration.json GET /auth-config Retrieve the authentication configuration settings. # Get paginated campaigns Source: https://docs.nudj.cx/api-reference/integration/get-campaigns openapi/integration.json GET /campaigns Retrieve a paginated list of campaigns. # Get paginated challenge groups Source: https://docs.nudj.cx/api-reference/integration/get-challenge-groups openapi/integration.json GET /challenge-groups Retrieve a paginated list of challenge groups with filtered challenges. # Get challenge group by ID Source: https://docs.nudj.cx/api-reference/integration/get-challenge-groups-challengeGroupId openapi/integration.json GET /challenge-groups/{challengeGroupId} Retrieve a challenge group by its ID with filtered challenges. # Get paginated challenges Source: https://docs.nudj.cx/api-reference/integration/get-challenges openapi/integration.json GET /challenges Retrieve a paginated list of challenges. # Get challenge by ID Source: https://docs.nudj.cx/api-reference/integration/get-challenges-challengeId openapi/integration.json GET /challenges/{challengeId} Retrieve a challenge by its ID. # Get paginated communities Source: https://docs.nudj.cx/api-reference/integration/get-communities openapi/integration.json GET /communities Retrieve a paginated list of communities. # Get community by ID or slug Source: https://docs.nudj.cx/api-reference/integration/get-communities-identifier openapi/integration.json GET /communities/{identifier} Retrieve a community by its ID or slug. # Get paginated events Source: https://docs.nudj.cx/api-reference/integration/get-events openapi/integration.json GET /events Retrieve a paginated list of events. # Get all feature flags Source: https://docs.nudj.cx/api-reference/integration/get-feature-flags openapi/integration.json GET /feature-flags Retrieve the status of all feature flags. # Get language configurations Source: https://docs.nudj.cx/api-reference/integration/get-languages openapi/integration.json GET /languages Retrieve all language configurations for the current organization. Empty translation objects ({}) mean 'use default translations'. # Get aggregated leaderboard data Source: https://docs.nudj.cx/api-reference/integration/get-leaderboards-configId-data openapi/integration.json GET /leaderboards/{configId}/data Retrieve aggregated leaderboard data for a specific configuration. # Get user's position in leaderboard Source: https://docs.nudj.cx/api-reference/integration/get-leaderboards-configId-position openapi/integration.json GET /leaderboards/{configId}/position Retrieves the authenticated user's position and score in the specified leaderboard # Get paginated leaderboard configurations Source: https://docs.nudj.cx/api-reference/integration/get-leaderboards-configs openapi/integration.json GET /leaderboards/configs Retrieve a paginated list of leaderboard configurations. # Get legal policies Source: https://docs.nudj.cx/api-reference/integration/get-legal-policies openapi/integration.json GET /legal-policies Retrieve the legal URLs and policies. # Get my info Source: https://docs.nudj.cx/api-reference/integration/get-me openapi/integration.json GET /me Retrieve the current user's information. # Get my paginated badges Source: https://docs.nudj.cx/api-reference/integration/get-me-badges openapi/integration.json GET /me/badges Retrieve a paginated list of the current user's badges. # Get my paginated reward entries Source: https://docs.nudj.cx/api-reference/integration/get-me-reward-entries openapi/integration.json GET /me/reward-entries Retrieve a paginated list of the current user's reward entries. # Get my reward entry by id Source: https://docs.nudj.cx/api-reference/integration/get-me-reward-entries-rewardEntryId openapi/integration.json GET /me/reward-entries/{rewardEntryId} Retrieve a reward entry by its ID for the current user. # Get my paginated rewards Source: https://docs.nudj.cx/api-reference/integration/get-me-rewards openapi/integration.json GET /me/rewards Retrieve a paginated list of the current user's rewards (excluding badges). # Get my reward asset by id Source: https://docs.nudj.cx/api-reference/integration/get-me-rewards-rewardAssetId openapi/integration.json GET /me/rewards/{rewardAssetId} Retrieve a reward asset by its ID for the current user. # Get my streak progress Source: https://docs.nudj.cx/api-reference/integration/get-me-streaks openapi/integration.json GET /me/streaks Retrieve the current user's streak progress. # Get paginated posts Source: https://docs.nudj.cx/api-reference/integration/get-posts openapi/integration.json GET /posts Retrieve a paginated list of posts. # Get post by id Source: https://docs.nudj.cx/api-reference/integration/get-posts-postId openapi/integration.json GET /posts/{postId} Retrieve a post by its ID. # Get paginated comments by post id Source: https://docs.nudj.cx/api-reference/integration/get-posts-postId-comments openapi/integration.json GET /posts/{postId}/comments Retrieve a paginated list of comments by post id. # Get paginated rewards Source: https://docs.nudj.cx/api-reference/integration/get-rewards openapi/integration.json GET /rewards Retrieve a paginated list of rewards. # Get reward by id Source: https://docs.nudj.cx/api-reference/integration/get-rewards-rewardId openapi/integration.json GET /rewards/{rewardId} Retrieve a reward by its ID. # Get paginated tags Source: https://docs.nudj.cx/api-reference/integration/get-tags openapi/integration.json GET /tags Retrieve a paginated list of tags. # Get theme configuration Source: https://docs.nudj.cx/api-reference/integration/get-themes openapi/integration.json GET /themes Retrieve the theme and styling configuration for the current organisation. # Get third party configuration Source: https://docs.nudj.cx/api-reference/integration/get-third-party-config openapi/integration.json GET /third-party-config Retrieve the third party integration settings. # Get paginated variables Source: https://docs.nudj.cx/api-reference/integration/get-variables openapi/integration.json GET /variables Retrieve a paginated list of variables. # Update my info Source: https://docs.nudj.cx/api-reference/integration/patch-me openapi/integration.json PATCH /me Update the current user's information. # Claim achievement rewards Source: https://docs.nudj.cx/api-reference/integration/post-achievements-achievementId-claim openapi/integration.json POST /achievements/{achievementId}/claim Claim rewards for a completed achievement. # Create action participation Source: https://docs.nudj.cx/api-reference/integration/post-action-participations openapi/integration.json POST /action-participations Create a new action participation. # Complete challenge for user Source: https://docs.nudj.cx/api-reference/integration/post-challenges-challengeId-complete openapi/integration.json POST /challenges/{challengeId}/complete Validate and complete a challenge for user if all actions are done. Idempotent and safe against concurrent requests. # Start challenge for user Source: https://docs.nudj.cx/api-reference/integration/post-challenges-challengeId-start openapi/integration.json POST /challenges/{challengeId}/start Start a challenge or new attempt for the authenticated user. Returns error if challenge is already in progress. # Unlock a challenge by spending keys Source: https://docs.nudj.cx/api-reference/integration/post-challenges-id-unlock openapi/integration.json POST /challenges/{id}/unlock Spend keys to unlock a challenge. Returns the amount of keys spent and the new challenge status. # Add a new comment Source: https://docs.nudj.cx/api-reference/integration/post-comments openapi/integration.json POST /comments Add a new comment to a discussion. # Claim an event token Source: https://docs.nudj.cx/api-reference/integration/post-events-claim openapi/integration.json POST /events/claim Claims an event token and logs the event for the user. # Accept community rules Source: https://docs.nudj.cx/api-reference/integration/post-me-communities-communityId-accept-rules openapi/integration.json POST /me/communities/{communityId}/accept-rules Accept the rules for a specific community. This operation is idempotent. # Check my reward entry Source: https://docs.nudj.cx/api-reference/integration/post-me-reward-entries-rewardEntryId-check openapi/integration.json POST /me/reward-entries/{rewardEntryId}/check Check if the current user's entry is a winning entry. # Redeem my reward Source: https://docs.nudj.cx/api-reference/integration/post-me-rewards-rewardAssetId-redeem openapi/integration.json POST /me/rewards/{rewardAssetId}/redeem Redeem a reward that the current user owns. # Add a new comment on a post Source: https://docs.nudj.cx/api-reference/integration/post-posts-postId-comments openapi/integration.json POST /posts/{postId}/comments Add a new comment on a post. # Like a post Source: https://docs.nudj.cx/api-reference/integration/post-posts-postId-like openapi/integration.json POST /posts/{postId}/like Like a post by its ID. # Purchase a reward Source: https://docs.nudj.cx/api-reference/integration/post-rewards-rewardId-purchase openapi/integration.json POST /rewards/{rewardId}/purchase Purchase entries or assets for a reward using points. # Create or update a variable value Source: https://docs.nudj.cx/api-reference/integration/post-variables-variableId openapi/integration.json POST /variables/{variableId} Creates a new variable value or updates an existing one if it already exists for the given variable. # Update a comment Source: https://docs.nudj.cx/api-reference/integration/put-comments-commentId openapi/integration.json PUT /comments/{commentId} Update an existing comment. # Update a comment on a post Source: https://docs.nudj.cx/api-reference/integration/put-posts-postId-comments-commentId openapi/integration.json PUT /posts/{postId}/comments/{commentId} Update an existing comment on a post. # Internationalization Source: https://docs.nudj.cx/api-reference/internationalization Request translated response bodies with the x-language header The Nudj APIs localise user-facing fields (challenge titles, reward descriptions, achievement copy, email templates, etc.) on the way out. Send the `x-language` header to control which language the API returns. ## The `x-language` header ```http theme={null} x-language: en ``` * Header name is case-insensitive. `x-lang` is accepted as a shortcut. * Value is a two-letter language code (`en`, `fr`, `de`, `es`, `ja`, `zh`, …) — the exact set supported by your organisation depends on the languages configured on your communities. * If the header is missing, the API falls back to the organisation's default language. * If the value is an unsupported language, the API falls back to the organisation's default language (no error). ## Which endpoints support it? Both the Integration and Admin APIs respect `x-language` on every endpoint that returns translatable content — challenges, actions, rewards, achievements, emails, leaderboard configs, community rules, variable labels, and any custom copy written through the admin dashboard. The Analytics API returns raw metrics and is not affected by `x-language`. ## Sending the header ```bash cURL theme={null} curl -X GET "https://{subdomain}.nudj.cx/api/v2/integration/challenges" \ -H "Authorization: Bearer $NUDJ_API_TOKEN" \ -H "x-language: fr" ``` ```javascript JavaScript theme={null} const response = await fetch( `https://${DOMAIN}/api/v2/integration/challenges`, { headers: { 'Authorization': `Bearer ${process.env.NUDJ_API_TOKEN}`, 'x-language': userLanguage, // e.g. 'fr' from your user context 'Content-Type': 'application/json', }, } ); ``` ```python Python theme={null} import os, requests response = requests.get( f"https://{DOMAIN}/api/v2/integration/challenges", headers={ "Authorization": f"Bearer {os.environ['NUDJ_API_TOKEN']}", "x-language": user_language, }, ) ``` ## Response shape Translatable fields come back pre-resolved — a single string in the requested language — rather than a map of `{ en: …, fr: … }`. Your client doesn't need to do any client-side locale matching: ```json theme={null} { "id": "…", "title": "Défi de la semaine", "description": "Complétez 3 actions pour gagner 100 XP" } ``` If a specific field has no translation for the requested language, it falls back to the community's default language for that field, then to the organisation's default language. ## Best practices 1. **Send `x-language` from your user context.** In a widget or integration that has a user's locale preference, pass it through on every request — the API handles the fallback chain for you. 2. **Don't pin every request to the organisation default.** If you omit the header, you always get the org default. That's fine for backend-only jobs (e.g. generating a CSV export), but for user-facing flows, send the user's actual locale. 3. **Cache per-language.** If you cache API responses on your side, make `x-language` part of the cache key — the same endpoint will return different payloads for different languages. ## Managing translations Translations themselves are managed in the admin dashboard. See [Questions & Variables](/admin-guide/settings/questions-variables) for the template system used across emails and community copy. ## Related Full auth header matrix (includes every supported request header). The `limit`/`skip` contract on list endpoints. # API Reference Source: https://docs.nudj.cx/api-reference/introduction Complete reference for Nudj Platform APIs The Nudj Platform exposes three JSON-over-HTTPS APIs for building gamification experiences, managing communities, and analysing engagement data. Every endpoint lives under your organisation's subdomain and accepts either `Authorization: Bearer` or `x-api-token` for authentication — see [Authentication](/api-reference/authentication) for the full matrix. ## Available APIs User-scoped operations — challenges, rewards, achievements, and profile data for logged-in members Administrative operations for platform configuration, content management, and RBAC Reporting endpoints for engagement metrics, leaderboards, and challenge performance ## Base URL Every request is scoped to your organisation's subdomain: ``` https://{your-subdomain}.nudj.cx/api/v2/{api-type}/{endpoint} ``` * **`{your-subdomain}`** is assigned when your organisation is provisioned (e.g. `acme-corp`). White-label custom domains are configurable via the Admin API's `/domains` endpoints — see [Custom Domains](/api-reference/custom-domains). * **`{api-type}`** is one of `integration`, `admin`, or `analytics`. * All requests must hit the subdomain that matches the token — cross-tenant token reuse is rejected at the auth middleware. ## Making Your First Request The example below uses `Authorization: Bearer` (recommended for new integrations). The legacy `x-api-token` header is also accepted everywhere — the runtime strips the `Bearer ` prefix automatically if present. ```javascript JavaScript theme={null} // Replace '{subdomain}' with your organisation's subdomain const response = await fetch('https://{subdomain}.nudj.cx/api/v2/integration/challenges', { headers: { 'Authorization': `Bearer ${process.env.NUDJ_API_TOKEN}`, 'Content-Type': 'application/json' } }); const challenges = await response.json(); console.log(challenges); ``` ```python Python theme={null} import os import requests # Replace '{subdomain}' with your organisation's subdomain API_TOKEN = os.environ['NUDJ_API_TOKEN'] response = requests.get( 'https://{subdomain}.nudj.cx/api/v2/integration/challenges', headers={ 'Authorization': f'Bearer {API_TOKEN}', 'Content-Type': 'application/json' } ) challenges = response.json() print(challenges) ``` ```bash cURL theme={null} # Replace '{subdomain}' with your organisation's subdomain curl -X GET https://{subdomain}.nudj.cx/api/v2/integration/challenges \ -H "Authorization: Bearer ${NUDJ_API_TOKEN}" \ -H "Content-Type: application/json" ``` ## Next Steps Bearer tokens, legacy `x-api-token`, per-API auth matrix, and best practices The tRPC error envelope and common status codes The `limit` / `skip` contract and `totalCount` + `edges` response shape Domain patterns, white-label URLs, and how tokens are scoped to hosts Outbound events, signing, retries, and idempotency The `x-language` header and translated response bodies # Get language configurations Source: https://docs.nudj.cx/api-reference/language/get-language-configurations /openapi/admin.json get /languages Retrieve all language configurations with full merged translations. Each language config contains the complete translation set (base locale merged with organisation-specific overrides). # Update language configuration Source: https://docs.nudj.cx/api-reference/language/update-language-configuration /openapi/admin.json post /languages/{language} Update or replace a language configuration. Omit translation to preserve existing overrides. Empty translation object means 'use default translations'. # Assign distribution details to a leaderboard config Source: https://docs.nudj.cx/api-reference/leaderboard/assign-distribution-details-to-a-leaderboard-config /openapi/admin.json post /leaderboards/{leaderboardConfigId}/distribution Assign distribution details to a leaderboard config. # Create a new leaderboard configuration Source: https://docs.nudj.cx/api-reference/leaderboard/create-a-new-leaderboard-configuration /openapi/admin.json post /leaderboards Creates a leaderboard configuration that defines how user data should be aggregated # Create default leaderboard configs Source: https://docs.nudj.cx/api-reference/leaderboard/create-default-leaderboard-configs /openapi/admin.json post /leaderboards/default Create default leaderboard configs # Export fresh leaderboard rankings Source: https://docs.nudj.cx/api-reference/leaderboard/export-fresh-leaderboard-rankings /openapi/admin.json post /leaderboards/export Calculates the selected period once from a fresh database snapshot and streams a complete CSV. Later writes are not included. Requires an authenticated admin user with permission to read XP logs, users and the leaderboard configuration. User identity fields retain community restrictions. Rankings exports allow up to 50000 participants and 64 MiB; exclusions templates allow 10000 rows and 5120000 bytes. Calculation has a 240-second request deadline. No partial file is returned if preparation fails. # Get aggregated leaderboard data Source: https://docs.nudj.cx/api-reference/leaderboard/get-aggregated-leaderboard-data /openapi/admin.json get /leaderboards/{configId}/data Retrieve aggregated leaderboard data for a specific configuration. # Get distribution details for a leaderboard config Source: https://docs.nudj.cx/api-reference/leaderboard/get-distribution-details-for-a-leaderboard-config /openapi/admin.json get /leaderboards/{leaderboardConfigId}/distribution Get distribution details for a leaderboard config. # Get leaderboard config by ID Source: https://docs.nudj.cx/api-reference/leaderboard/get-leaderboard-config-by-id /openapi/admin.json get /leaderboards/configs/{leaderboardConfigId} Retrieve a leaderboard config by its ID. # Get paginated leaderboard configurations Source: https://docs.nudj.cx/api-reference/leaderboard/get-paginated-leaderboard-configurations /openapi/admin.json get /leaderboards/configs # Get user's position in leaderboard Source: https://docs.nudj.cx/api-reference/leaderboard/get-users-position-in-leaderboard /openapi/integration.json get /leaderboards/{configId}/position Retrieves the authenticated user's position and score in the specified leaderboard # Update a leaderboard configuration Source: https://docs.nudj.cx/api-reference/leaderboard/update-a-leaderboard-configuration /openapi/admin.json patch /leaderboards/configs/{id} Updates an existing leaderboard configuration with the provided data # Get legal policies Source: https://docs.nudj.cx/api-reference/legal-policies/get-legal-policies /openapi/admin.json get /legal-policies Retrieve the legal URLs and policies. # Update legal policies Source: https://docs.nudj.cx/api-reference/legal-policies/update-legal-policies /openapi/admin.json patch /legal-policies Update the legal URLs and policies. # Create a new organisation Source: https://docs.nudj.cx/api-reference/organisation/create-a-new-organisation /openapi/admin.json post /organisations Create a new organisation. # Get organisation by ID Source: https://docs.nudj.cx/api-reference/organisation/get-organisation-by-id /openapi/admin.json get /organisations/{organisationId} Retrieve an organisation by its ID. # Update an organisation Source: https://docs.nudj.cx/api-reference/organisation/update-an-organisation /openapi/admin.json patch /organisations/{id} Update an existing organisation. # Pagination Source: https://docs.nudj.cx/api-reference/pagination The limit/skip contract and response shapes used by list endpoints Most list endpoints across the Integration and Admin APIs share a common pagination contract. This page documents the shape so you can build a single helper and reuse it everywhere. ## Query parameters | Parameter | Type | Default | Notes | | - | - | - | - | | `limit` | integer | `10` | Maximum number of items to return. Upper bound is typically `100`. | | `skip` | integer | `0` | Number of items to skip before returning the page. | | `search` | string | — | Case-insensitive substring match on the entity's primary text fields. | | `sort` | string | endpoint-specific | A sort directive (e.g. `-createdAt` for descending). Not every list endpoint supports arbitrary sort keys — check the endpoint's schema. | Date-range filters use the conventions `fromDate` / `toDate` on most endpoints. A few older endpoints use `startDate` / `endDate` — check the endpoint page for the canonical parameter names. ## Response shape Most paginated endpoints return an envelope: ```json theme={null} { "totalCount": 142, "edges": [ { /* entity */ }, { /* entity */ } ] } ``` | Field | Description | | - | - | | `totalCount` | Total number of matching entities, ignoring `limit` and `skip`. Use this to drive pagination UI. | | `edges` | Array of entities for the current page. Array length is at most `limit`. | A handful of older list endpoints return `{ items }` or a bare array instead of `{ totalCount, edges }`. The endpoint page will reflect the actual schema — always prefer the per-endpoint schema over assumptions. ## Requesting a page ```bash cURL theme={null} curl -X GET "https://{subdomain}.nudj.cx/api/v2/admin/challenges?limit=20&skip=40" \ -H "Authorization: Bearer $NUDJ_API_TOKEN" \ -H "Content-Type: application/json" ``` ```javascript JavaScript theme={null} async function listChallenges({ limit = 20, skip = 0 } = {}) { const url = new URL(`https://${DOMAIN}/api/v2/admin/challenges`); url.searchParams.set('limit', limit); url.searchParams.set('skip', skip); const response = await fetch(url, { headers: { 'Authorization': `Bearer ${process.env.NUDJ_API_TOKEN}` }, }); return response.json(); // { totalCount, edges } } ``` ```python Python theme={null} import os, requests def list_challenges(limit=20, skip=0): response = requests.get( f"https://{DOMAIN}/api/v2/admin/challenges", params={"limit": limit, "skip": skip}, headers={"Authorization": f"Bearer {os.environ['NUDJ_API_TOKEN']}"}, ) return response.json() # { totalCount, edges } ``` ## Paginating through every page ```javascript theme={null} async function* paginateAll(path, pageSize = 100) { let skip = 0; while (true) { const page = await nudjRequest(`${path}?limit=${pageSize}&skip=${skip}`); for (const edge of page.edges) yield edge; skip += page.edges.length; if (skip >= page.totalCount || page.edges.length === 0) return; } } for await (const challenge of paginateAll('/api/v2/admin/challenges')) { // … } ``` ## Common errors | Status | Cause | Fix | | - | - | - | | `400 BAD_REQUEST` — `limit` must be a positive integer | Non-integer or negative `limit` | Use a positive integer, capped at 100 | | `400 BAD_REQUEST` — `skip` must be non-negative | Negative `skip` | Use `0` or a positive integer | | `400 BAD_REQUEST` — Invalid sort key | Endpoint doesn't support the requested sort | Check the endpoint's schema for supported `sort` values | See [Errors](/api-reference/errors) for the full envelope. ## Related Error envelope and common status codes. Translating response bodies with `x-language`. # Create a new post Source: https://docs.nudj.cx/api-reference/post/create-a-new-post /openapi/admin.json post /posts Create a new post. # Get paginated comments by post id Source: https://docs.nudj.cx/api-reference/post/get-paginated-comments-by-post-id /openapi/integration.json get /posts/{postId}/comments Retrieve a paginated list of comments by post id. # Get paginated posts Source: https://docs.nudj.cx/api-reference/post/get-paginated-posts /openapi/admin.json get /posts Retrieve a paginated list of posts. # Get post by id Source: https://docs.nudj.cx/api-reference/post/get-post-by-id /openapi/admin.json get /posts/{postId} Retrieve a post by its ID. # Like a post Source: https://docs.nudj.cx/api-reference/post/like-a-post /openapi/integration.json post /posts/{postId}/like Like a post by its ID. # Unlike a post Source: https://docs.nudj.cx/api-reference/post/unlike-a-post /openapi/integration.json delete /posts/{postId}/like Unlike a post by its ID. # Update a post by id Source: https://docs.nudj.cx/api-reference/post/update-a-post-by-id /openapi/admin.json post /posts/{id} Update a post by id. # Create a reward Source: https://docs.nudj.cx/api-reference/reward/create-a-reward /openapi/admin.json post /rewards Create a new reward. # Get my paginated rewards Source: https://docs.nudj.cx/api-reference/reward/get-my-paginated-rewards /openapi/integration.json get /me/rewards Retrieve a paginated list of the current user's rewards (excluding badges). # Get my reward asset by id Source: https://docs.nudj.cx/api-reference/reward/get-my-reward-asset-by-id /openapi/integration.json get /me/rewards/{rewardAssetId} Retrieve a reward asset by its ID for the current user. # Get paginated reward entries for a specific reward Source: https://docs.nudj.cx/api-reference/reward/get-paginated-reward-entries-for-a-specific-reward /openapi/admin.json get /rewards/{rewardId}/entries Retrieves a paginated list of all values for a given variable configuration. # Get paginated reward redemption values for a specific reward Source: https://docs.nudj.cx/api-reference/reward/get-paginated-reward-redemption-values-for-a-specific-reward /openapi/admin.json get /rewards/{rewardId}/redemption-values Retrieves a paginated list of redemption values for a given reward. # Get paginated rewards Source: https://docs.nudj.cx/api-reference/reward/get-paginated-rewards /openapi/admin.json get /rewards Retrieve a paginated list of rewards. # Get reward allocations overview Source: https://docs.nudj.cx/api-reference/reward/get-reward-allocations-overview /openapi/analytics.json get /rewards/stats/allocations Get detailed allocation analytics for a specific reward including source information and distribution statistics # Get reward by ID Source: https://docs.nudj.cx/api-reference/reward/get-reward-by-id /openapi/admin.json get /rewards/{rewardId} Retrieve a reward by its ID. # Get reward recipients Source: https://docs.nudj.cx/api-reference/reward/get-reward-recipients /openapi/analytics.json get /rewards/stats/recipients Get list of users who received a specific reward with their quantities and dates # Get reward timeline data Source: https://docs.nudj.cx/api-reference/reward/get-reward-timeline-data /openapi/analytics.json get /rewards/stats/timeline Get daily timeline data showing rewards earned and redeemed over time # Giveaway participants list Source: https://docs.nudj.cx/api-reference/reward/giveaway-participants-list /openapi/analytics.json get /rewards/stats/giveaways/participants Get detailed list of giveaway participants with their entry counts, sources, and win/loss status # Giveaways overview analytics Source: https://docs.nudj.cx/api-reference/reward/giveaways-overview-analytics /openapi/analytics.json get /rewards/stats/giveaways/overview Get comprehensive analytics for giveaways including participation, entry distribution, winners, and time series data # Process manual reward distribution Source: https://docs.nudj.cx/api-reference/reward/process-manual-reward-distribution /openapi/admin.json post /rewards/manual-distribution Process a manual distirbution for a set of rewards to specific users. # Process manual reward giveaway Source: https://docs.nudj.cx/api-reference/reward/process-manual-reward-giveaway /openapi/admin.json post /rewards/{rewardId}/process-giveaway Process a manual giveaway for a reward to specific users. Requires a list of user IDs and the quantity of rewards to distribute to each user. # Process reward redemption values from CSV Source: https://docs.nudj.cx/api-reference/reward/process-reward-redemption-values-from-csv /openapi/admin.json post /rewards/{rewardId}/redemption-values Upload and process reward redemption values from a CSV file. The CSV file should have one redemption code per line, with no headers. # Purchase a reward Source: https://docs.nudj.cx/api-reference/reward/purchase-a-reward /openapi/integration.json post /rewards/{rewardId}/purchase Purchase entries or assets for a reward using points. # Redeem my reward Source: https://docs.nudj.cx/api-reference/reward/redeem-my-reward /openapi/integration.json post /me/rewards/{rewardAssetId}/redeem Redeem a reward that the current user owns. # Reward distribution events overview Source: https://docs.nudj.cx/api-reference/reward/reward-distribution-events-overview /openapi/analytics.json get /rewards/stats/distribution/overview Get analytics for reward distribution events including success rates, event types, and performance metrics # Rewards assets overview Source: https://docs.nudj.cx/api-reference/reward/rewards-assets-overview /openapi/analytics.json get /rewards/stats/assets/overview Get comprehensive analytics for reward assets including claims, redemptions, distribution sources, and time series data # Rewards assets redemption analytics Source: https://docs.nudj.cx/api-reference/reward/rewards-assets-redemption-analytics /openapi/analytics.json get /rewards/stats/assets/redemptions Get detailed redemption analytics including redemption types, time to redeem distribution, and expiration analysis # Update a reward by id Source: https://docs.nudj.cx/api-reference/reward/update-a-reward-by-id /openapi/admin.json post /rewards/{id} Update a reward by id. # User rewards analytics Source: https://docs.nudj.cx/api-reference/reward/user-rewards-analytics /openapi/analytics.json get /rewards/stats/users Get user-level reward analytics including portfolio, behavior patterns, and historical performance # Check my reward entry Source: https://docs.nudj.cx/api-reference/rewardentry/check-my-reward-entry /openapi/integration.json post /me/reward-entries/{rewardEntryId}/check Check if the current user's entry is a winning entry. # Get my paginated reward entries Source: https://docs.nudj.cx/api-reference/rewardentry/get-my-paginated-reward-entries /openapi/integration.json get /me/reward-entries Retrieve a paginated list of the current user's reward entries. # Get my reward entry by id Source: https://docs.nudj.cx/api-reference/rewardentry/get-my-reward-entry-by-id /openapi/integration.json get /me/reward-entries/{rewardEntryId} Retrieve a reward entry by its ID for the current user. # Generate Shopify linking code Source: https://docs.nudj.cx/api-reference/shopify/generate-shopify-linking-code /openapi/admin.json post /third-party-config/initiate-shopify-connection Creates or replaces linking code for Shopify account connection. Code expires in 15 minutes. # Get my streak progress Source: https://docs.nudj.cx/api-reference/streak/get-my-streak-progress /openapi/integration.json get /me/streaks Retrieve the current user's streak progress. # Get paginated streak configs Source: https://docs.nudj.cx/api-reference/streak/get-paginated-streak-configs /openapi/admin.json get /streaks/configs Retrieve a paginated list of streak configs. # Get streak config by ID Source: https://docs.nudj.cx/api-reference/streak/get-streak-config-by-id /openapi/admin.json get /streaks/configs/{id} Retrieve a streak config by its ID. # Update a streak config by id Source: https://docs.nudj.cx/api-reference/streak/update-a-streak-config-by-id /openapi/admin.json patch /streaks/configs/{id} Update a streak config by id. # Create a new tag Source: https://docs.nudj.cx/api-reference/tag/create-a-new-tag /openapi/admin.json post /tags Create a new tag. # Delete a tag by id Source: https://docs.nudj.cx/api-reference/tag/delete-a-tag-by-id /openapi/admin.json delete /tags/{id} Delete a tag by id. This is permanent and cannot be undone. # Get paginated tags Source: https://docs.nudj.cx/api-reference/tag/get-paginated-tags /openapi/admin.json get /tags Retrieve a paginated list of tags. # Get tag by ID Source: https://docs.nudj.cx/api-reference/tag/get-tag-by-id /openapi/admin.json get /tags/{id} Retrieve a specific tag by its ID. # Update a tag by ID Source: https://docs.nudj.cx/api-reference/tag/update-a-tag-by-id /openapi/admin.json patch /tags/{id} Update specific fields of a tag by its ID. # Get paginated team members Source: https://docs.nudj.cx/api-reference/team/get-paginated-team-members /openapi/admin.json get /teams Retrieve a paginated list of team members for the organisation. # Invite users to an organisation Source: https://docs.nudj.cx/api-reference/team/invite-users-to-an-organisation /openapi/admin.json post /teams/invite Invite users to an organisation by email address. # Remove a user from an organisation Source: https://docs.nudj.cx/api-reference/team/remove-a-user-from-an-organisation /openapi/admin.json delete /teams/{userId} Remove a user from an organisation by removing their role. # Create a new entity from a template Source: https://docs.nudj.cx/api-reference/templates/create-a-new-entity-from-a-template /openapi/admin.json post /templates/create-entity Create a new entity (community, challenge, achievement, action, post, or reward) from an existing template. # Get paginated achievement templates Source: https://docs.nudj.cx/api-reference/templates/get-paginated-achievement-templates /openapi/admin.json get /templates/achievements Get a paginated list of achievement templates available to your organisation. # Get paginated action templates Source: https://docs.nudj.cx/api-reference/templates/get-paginated-action-templates /openapi/admin.json get /templates/actions Get a paginated list of action templates available to your organisation. # Get paginated challenge templates Source: https://docs.nudj.cx/api-reference/templates/get-paginated-challenge-templates /openapi/admin.json get /templates/challenges Get a paginated list of challenge templates available to your organisation. # Get paginated community templates Source: https://docs.nudj.cx/api-reference/templates/get-paginated-community-templates /openapi/admin.json get /templates/communities Get a paginated list of community templates available to your organisation. # Get paginated post templates Source: https://docs.nudj.cx/api-reference/templates/get-paginated-post-templates /openapi/admin.json get /templates/posts Get a paginated list of post templates available to your organisation. # Get paginated reward templates Source: https://docs.nudj.cx/api-reference/templates/get-paginated-reward-templates /openapi/admin.json get /templates/rewards Get a paginated list of reward templates available to your organisation. # Get theme configuration Source: https://docs.nudj.cx/api-reference/theme/get-theme-configuration /openapi/admin.json get /themes Retrieve the theme and styling configuration. # Update theme configuration Source: https://docs.nudj.cx/api-reference/theme/update-theme-configuration /openapi/admin.json patch /themes Update the theme and styling configuration. # Get third party configuration Source: https://docs.nudj.cx/api-reference/third-party-config/get-third-party-configuration /openapi/admin.json get /third-party-config Retrieve the third party configuration settings. # Update third party configuration Source: https://docs.nudj.cx/api-reference/third-party-config/update-third-party-configuration /openapi/admin.json patch /third-party-config Update the third party configuration settings. # Accept community rules Source: https://docs.nudj.cx/api-reference/user/accept-community-rules /openapi/integration.json post /me/communities/{communityId}/accept-rules Accept the rules for a specific community. This operation is idempotent. # Create user Source: https://docs.nudj.cx/api-reference/user/create-user /openapi/admin.json post /users Create a new user. # Delete user personal data Source: https://docs.nudj.cx/api-reference/user/delete-user-personal-data /openapi/admin.json delete /users/{id} Delete user personal data while keeping the user record for analytics. This anonymizes the user by removing personal information and replacing email/username with deleted user indicators. # Get my info Source: https://docs.nudj.cx/api-reference/user/get-my-info /openapi/integration.json get /me Retrieve the current user's information. # Get paginated users Source: https://docs.nudj.cx/api-reference/user/get-paginated-users /openapi/admin.json get /users Retrieve a paginated list of users. # Get user by ID Source: https://docs.nudj.cx/api-reference/user/get-user-by-id /openapi/admin.json get /users/{id} Retrieve a user by their ID. # Manage points transaction Source: https://docs.nudj.cx/api-reference/user/manage-points-transaction /openapi/admin.json post /users/{id}/points-transaction Create a points transaction (credit or debit) for a user's account # Sync user to external account Source: https://docs.nudj.cx/api-reference/user/sync-user-to-external-account /openapi/admin.json post /users/sync-to-external-account Sync a user to an external account. # Update my info Source: https://docs.nudj.cx/api-reference/user/update-my-info /openapi/integration.json patch /me Update the current user's information. # Get comprehensive user details Source: https://docs.nudj.cx/api-reference/users/get-comprehensive-user-details /openapi/analytics.json get /users/details Retrieves comprehensive user details including stats, achievements, challenges, rewards, XP breakdown, and activity history. # Get users overview Source: https://docs.nudj.cx/api-reference/users/get-users-overview /openapi/analytics.json get /users/overview Retrieves a paginated overview of users # List user activity (paginated) Source: https://docs.nudj.cx/api-reference/users/list-user-activity-paginated /openapi/analytics.json get /users/activity Returns a paginated list of user activities sorted by newest first. # List user points transactions (paginated) Source: https://docs.nudj.cx/api-reference/users/list-user-points-transactions-paginated /openapi/analytics.json get /users/points-transactions Returns a paginated list of user points transactions sorted by newest first. # Create a new variable config Source: https://docs.nudj.cx/api-reference/variable/create-a-new-variable-config /openapi/admin.json post /variables Creates a new variable configuration for collecting user data. # Get paginated variable configs Source: https://docs.nudj.cx/api-reference/variable/get-paginated-variable-configs /openapi/admin.json get /variables Retrieve a paginated list of variable configs. # Get paginated variable values for a specific variable config Source: https://docs.nudj.cx/api-reference/variable/get-paginated-variable-values-for-a-specific-variable-config /openapi/admin.json get /variables/{variableConfigId}/values Retrieves a paginated list of all values for a given variable configuration. # Get variable config by ID Source: https://docs.nudj.cx/api-reference/variable/get-variable-config-by-id /openapi/admin.json get /variables/{variableConfigId} Retrieve a variable config by its ID. # Update an existing variable config Source: https://docs.nudj.cx/api-reference/variable/update-an-existing-variable-config /openapi/admin.json patch /variables/{id} Updates an existing variable configuration. # Create or update a variable value Source: https://docs.nudj.cx/api-reference/variables/create-or-update-a-variable-value /openapi/integration.json post /variables/{variableId} Creates a new variable value or updates an existing one if it already exists for the given variable. # Get paginated variables Source: https://docs.nudj.cx/api-reference/variables/get-paginated-variables /openapi/integration.json get /variables Retrieve a paginated list of variables. # Webhooks Source: https://docs.nudj.cx/api-reference/webhooks Outbound webhook events and delivery model Nudj emits outbound webhooks to your customer systems when tracked events fire in the platform. This page is the API-reference overview — for the full event catalogue, payload shapes, signing, retry semantics, and step-by-step implementation, see the [Webhook Implementation Guide](/developer/webhooks/implementation). ## Overview End-to-end guide — configuring endpoints, verifying signatures, handling retries. The three endpoints for configuring webhooks and inspecting delivery logs. ## Admin API endpoints | Method | Path | Purpose | | - | - | - | | `GET` | `/webhooks/configs` | List every webhook configuration on your organisation. | | `PUT` | `/webhooks/configs` | Create or update a webhook configuration (target URL, enabled event types, secret). | | `GET` | `/webhooks/logs` | Paginated delivery log — every attempt, status code, and response body. | Find them in the [Admin API → Webhooks](/api-reference/admin/get-webhooks-configs) group in the sidebar. ## Delivery model * **At-least-once delivery.** Every webhook carries a deterministic nonce (event id) so your handler can idempotently dedupe. * **Retries with exponential backoff.** Failed deliveries retry on a QStash-backed queue — HTTP `5xx` and connection errors trigger retries, `4xx` responses do not. * **Signed payloads.** Every delivery is signed with your endpoint's shared secret. Verify the signature before processing — see [Webhook Implementation](/developer/webhooks/implementation). * **Ordering is not guaranteed.** Handlers must be order-independent; use the event's timestamp field for causal ordering within a stream. ## Event categories Profile creation, authentication, community joins, opt-in/opt-out. Action completion, hint reveal, repeatable action milestones. Challenge start, completion, recap generation. Reward distribution, claim, redemption, expiry. Achievement unlock, tier progression, streak milestones. Referral click, conversion, referrer/referee reward distribution. See the [webhook event catalogue](/developer/webhooks/event-catalog) for every event type and payload schema. ## Idempotency Every webhook includes a unique event id in the payload. The recommended pattern: 1. Store every processed event id in a short-TTL cache (e.g. Redis, 24h). 2. On receipt, check the cache — if already processed, return `200` without doing any work. 3. Otherwise, process the event, then write the id to the cache. This is safe against both duplicate deliveries from retries and any future changes to the delivery queue. ## Configuration flow Call `PUT /webhooks/configs` with your receiving URL, the event types you care about, and optionally a secret for HMAC signing. See the [Admin API reference](/api-reference/admin/put-webhooks-configs) for the full request schema. Trigger an action in the admin dashboard (or via the API) and inspect `GET /webhooks/logs` — every attempt is logged with the outgoing payload, target URL, response status code, and error body (if any). Follow the patterns in [Webhook Implementation](/developer/webhooks/implementation): verify the signature, dedupe on the nonce, reply `2xx` on success or `5xx` to trigger a retry. ## Related Full handler walkthrough — signatures, retries, debugging. OpenAPI reference for the three config endpoints. Error envelope for webhook config mutations. Admin API authentication for `/webhooks/*` endpoints. # Get paginated webhook logs Source: https://docs.nudj.cx/api-reference/webhooks/get-paginated-webhook-logs /openapi/admin.json get /webhooks/logs Retrieve a paginated list of webhook execution logs with optional filters. # Get webhook configurations Source: https://docs.nudj.cx/api-reference/webhooks/get-webhook-configurations /openapi/admin.json get /webhooks/configs Retrieve the webhook configurations for the organisation. # Update webhook configurations Source: https://docs.nudj.cx/api-reference/webhooks/update-webhook-configurations /openapi/admin.json put /webhooks/configs Update the webhook configuration for the organisation. # Get XP configuration Source: https://docs.nudj.cx/api-reference/xp-config/get-xp-configuration /openapi/admin.json get /xp-config Retrieve the XP configuration settings. # Update XP configuration Source: https://docs.nudj.cx/api-reference/xp-config/update-xp-configuration /openapi/admin.json patch /xp-config Update the XP configuration settings. # API Link User Token Authentication Source: https://docs.nudj.cx/developer/api-link-user-authentication Auto-sign authenticated users into Nudj using JWT tokens For the current widget integration, use an HS256 identity JWT with the signing key ID in the `kid` header, the authenticated external user ID in `sub`, and a five-minute lifetime. Merchant identity tokens have a ten-minute maximum. The [worked game integration](/developer/game-integration) shows inline embedding, native member sessions and signed event claims, including the distinction between each token. Never log token values or token-bearing URLs. This guide explains how to authenticate users directly into Nudj using JWT tokens that encode user metadata. Use this when your users are already authenticated in your system and you want to pass them into Nudj seamlessly. **Use this method if**: Users are already logged into your platform and you want to continue their session in Nudj without showing a login screen. **If users arrive at Nudj directly to log in**, use [OAuth Integration Setup](/developer/oauth-authentication) instead. ## Quick Start 1. Get your `clientId` and `clientSecret` from Nudj API Configuration 2. Generate a signed JWT containing user details using your `clientSecret` 3. Build the auto-login URL with the token and `clientId` 4. Redirect the user to that URL 5. They land in Nudj fully signed in ## When to Use This Method Use API Link if: * Users are already authenticated in your platform * You want to pass them into Nudj without showing a login screen * You're embedding Nudj in your app or redirecting from your dashboard * You want the fastest, most seamless user experience **Don't use this if** users arrive at Nudj directly without being authenticated—in that case, use [OAuth Integration Setup](/developer/oauth-authentication). ## Retrieve Your Credentials 1. Log into your Nudj admin panel 2. Navigate to **Organisation Settings → API Configuration** 3. Copy these values: * **Client ID** * **Client Secret** You'll need these to sign tokens and identify your organization. ## Generate the User Token The user token is a JWT (JSON Web Token) signed with your `clientSecret`. It must contain these properties: ```javascript theme={null} import { sign } from "jsonwebtoken"; const userData = { userId: "abc123", // ✅ REQUIRED: Unique ID for this user email: "user@example.com", // ⚠️ Optional but recommended locale: "en", // ✅ REQUIRED: Language code (e.g., "en", "fr", "es") }; function signUserToken(input) { const clientSecret = "YOUR_CLIENT_SECRET"; // From API Configuration return sign(input, clientSecret, { expiresIn: "5m" }); } const userToken = signUserToken(userData); // Use the token without logging it or a URL that contains it. ``` ### Token Properties | Property | Required? | Description | | - | - | - | | `userId` | ✅ Yes | Unique identifier for the user. Stored as `externalId` in Nudj. | | `email` | ⚠️ Optional | User's email address. Must be unique per user. If omitted, Nudj generates a placeholder email and the user can only log in via direct links. | | `locale` | ✅ Yes | Language/region code (e.g., "en", "fr", "es"). Defaults to "en" if not provided. | | `username` | ⚠️ Optional | Display name shown in Nudj UI. Defaults to "anonymous" until updated. | | `anonymousAccountId` | ⚠️ Optional | Provided by Nudj to merge an anonymous session with the user's account. | ## Auto-Sign the User Into Nudj Once you have the `userToken`, build the login URL: ```javascript theme={null} const domain = "loyalty.mycompany.com"; // Your Nudj domain const clientId = "YOUR_CLIENT_ID"; // From API Configuration const userToken = "YOUR_GENERATED_TOKEN"; // From previous step const linkUrl = `https://${domain}/api/link?userToken=${userToken}&clientId=${clientId}`; // Redirect the user to this URL window.location.href = linkUrl; ``` The user will be redirected to Nudj and automatically logged in. ## How It Works ### Account Identification Each user is identified by their `userId`. If a user with that `userId` already exists, their record is updated. If not, a new account is created. ### Account Creation When a new user arrives with an `API Link` token: 1. Nudj checks if a user with that `userId` already exists 2. If not, creates a new account with the provided details 3. The user is logged in immediately ### Account Updates If a user logs in again with updated information (different email, locale, username): * Nudj updates the existing user record with the new values * The user's existing data and history are preserved ### Email Handling * **If `email` is provided**: Nudj uses it to identify and email the user * **If `email` is omitted**: Nudj generates a placeholder email; the user can only log in via direct links (no magic link or self-serve login) ## Example Integration Flow Here's a complete example of how API Link authentication flows: Your user authenticates with your system (however you handle that) Your backend code signs a JWT with the user's details using your `clientSecret` Your frontend creates the login URL with the token and `clientId` Your frontend redirects the user to the Nudj login URL Nudj verifies the token, creates/updates the user, and logs them in—no login screen shown ## Code Example: Node.js Backend ```javascript theme={null} const express = require("express"); const { sign } = require("jsonwebtoken"); const app = express(); // These come from your Nudj API Configuration const NUDJ_CLIENT_ID = "your_client_id"; const NUDJ_CLIENT_SECRET = "your_client_secret"; const NUDJ_DOMAIN = "loyalty.mycompany.com"; // Endpoint that generates the login link app.get("/nudj-login-link", (req, res) => { // Get the authenticated user from your system const user = req.user; // Assuming you have authentication middleware // Create the token payload const tokenPayload = { userId: user.id, email: user.email, locale: user.preferredLanguage || "en", username: user.displayName, }; // Sign the token const userToken = sign(tokenPayload, NUDJ_CLIENT_SECRET, { expiresIn: "5m", }); // Build the Nudj login URL const loginUrl = `https://${NUDJ_DOMAIN}/api/link?userToken=${userToken}&clientId=${NUDJ_CLIENT_ID}`; // Return the URL to your frontend res.json({ loginUrl }); }); app.listen(3000, () => console.log("Server running")); ``` ## Code Example: React Frontend ```jsx theme={null} import { useEffect, useState } from "react"; export function NudjLoginButton() { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const handleLogin = async () => { setLoading(true); setError(null); try { // Call your backend to get the login URL const response = await fetch("/nudj-login-link"); const { loginUrl } = await response.json(); // Redirect to Nudj window.location.href = loginUrl; } catch (err) { setError("Failed to generate login link"); setLoading(false); } }; return (
{error &&

{error}

}
); } ``` ## Security Considerations Your `clientSecret` is sensitive. Only use it on your backend, never in frontend code or client-side JavaScript. Always use HTTPS for token generation and transmission. HTTP will expose tokens. Use a five-minute lifetime. Current merchant identity tokens are limited to ten minutes. Sanitize user data before putting it in the token. Never include sensitive information like passwords or API keys. ## Troubleshooting This usually means the token is invalid or the `clientId` is wrong. Verify: * The token was signed with your correct `clientSecret` * The `clientId` matches your API Configuration * The token hasn't expired * The token is being passed in the URL correctly The token signature is invalid. This means either: * You're using the wrong `clientSecret` to sign the token * The token was tampered with or corrupted * You're using a different algorithm than expected (use `HS256`, the default for `jsonwebtoken`) Verify your `clientSecret` is correct and hasn't been rotated. Some user properties are optional. If you didn't include `email`, Nudj generates a placeholder. If you didn't include `username`, it defaults to "anonymous". Include all desired properties in the token payload, and they'll be stored. Each `userId` should map to exactly one `email`. If you send the same `userId` with different emails, Nudj updates the user's email. This might cause issues if you expect a 1:1 mapping. Ensure your `userId` and `email` are always consistent for the same user. ## Next Steps 1. Get your API credentials from Nudj API Configuration 2. Implement token generation in your backend 3. Build the login URL on your frontend 4. Test with a real user 5. Monitor for errors in your logs Need help? Contact Nudj support. # Bearer Token Authentication Source: https://docs.nudj.cx/developer/bearer-token-auth Authenticate with the Admin API using Authorization: Bearer tokens and strict organisation scoping. The Admin API supports two authentication paths: 1. **`x-api-token`** — a long-lived JWT issued from **Settings → Developer → API Tokens**. Used by server-to-server scripts. 2. **`Authorization: Bearer `** — short-lived user-session tokens scoped to a single organisation. Used by MCP clients, the admin panel's internal calls, and any tooling that impersonates a logged-in user. This page covers the Bearer path. For the long-lived token path see [API Reference → Authentication](/api-reference/authentication). Use Bearer tokens when the caller represents a specific **user session** (e.g. an AI agent acting on behalf of a signed-in admin). Use `x-api-token` for headless integrations. ## How the two differ | | `x-api-token` | `Authorization: Bearer` | | - | - | - | | Issuer | Admin panel → Developer settings | Nudj auth service (NextAuth) at sign-in | | Audience | Scripts, CI, backend integrations | Interactive user sessions, MCP clients | | Lifetime | Long-lived (rotate manually) | Short-lived (session TTL) | | Organisation scope | Embedded in token claims | Enforced at the middleware by the token + `x-api-domain` combination | | Revocation | `POST /auth-config/tokens/{tokenId}/revoke` | Sign out or let the session expire | ## Making a request ```bash theme={null} curl -X GET "https://{subdomain}.nudj.cx/api/v2/admin/communities" \ -H "Authorization: Bearer YOUR_SESSION_TOKEN" \ -H "x-api-domain: https://{subdomain}.nudj.cx/api/v2/admin" ``` Two headers, both required: * `Authorization: Bearer ` — your session token. * `x-api-domain` — the exact API domain the token is scoped to. This tells Nudj which organisation's subdomain to route the request through. The server validates the token signature, verifies the requested org matches the token's claims, and rejects with `403` if they diverge. ## Organisation scoping (why this matters) Before bearer-token org scoping shipped, a session minted for org A could — under specific routing conditions — be used against org B's API if the caller changed the subdomain. That cross-tenant leak is closed: every Bearer request is checked at the middleware layer against both the token's claimed `organisationId` and the `x-api-domain` header. **Don't attempt to work around the scope check.** If you need to call a different organisation's API, sign in to that organisation and obtain a fresh token. Sharing tokens across orgs silently won't work and will always return `403 Forbidden`. ## Error cases | Status | Cause | | - | - | | `401 Unauthorized` | Missing header, token expired, or signature invalid | | `403 Forbidden` | Token valid but scoped to a different organisation than `x-api-domain` | | `404 Not Found` | `x-api-domain` points to a subdomain that doesn't exist | ## Where to get a token Bearer tokens are minted by the Nudj auth layer when a user signs in. Two common paths: 1. **Interactive admin session**: sign into the admin panel at `https://{subdomain}.nudj.cx/admin`. The session cookie is exchanged for a Bearer token on each admin API call automatically. 2. **MCP client**: follow the [MCP Bearer + Domain header pattern](/developer/mcp/bearer-and-domain) — `mcp-remote` and Claude Code inject both headers from a token copied out of the admin panel. ## Security considerations Treat Bearer tokens like session cookies. Never commit them, never log them, and never share them across organisations. * Store tokens in OS keychains or secret managers, never in source. * Rotate tokens when a team member leaves or any suspicion of compromise. * The middleware-level org scope check is a defence-in-depth measure, not a replacement for least-privilege RBAC on the token itself. ## Related The long-lived token path for headless integrations. How AI clients (Claude, Cursor) configure the headers. # Build a Game with Nudj Source: https://docs.nudj.cx/developer/game-integration A worked integration using Planet Nudj: authenticated players, signed gameplay events, native achievements and an inline widget. You can connect your own game to Nudj while keeping points, achievements and rewards in Nudj. Planet Nudj uses this pattern for a browser game in which you walk around a small planet, visit feature stations, race, cross stepping stones and find treasure. The live loyalty widget appears in the character's phone. This guide records the implementation and tests from 9 September 2026. It is a worked example, including its deployment limits, rather than a game SDK. ## How the game is built The website uses Next.js and React for the page and phone overlay, with Three.js for the world, spherical movement, collision geometry and animation. The scene reports actions to a same-origin Next.js route. That route validates progression, saves it and delivers signed events to Nudj. ```mermaid theme={null} sequenceDiagram participant P as Player and game participant S as Game server participant N as Nudj participant W as Widget in phone P->>S: Establish game session S->>N: API Link identity, then native session N-->>S: Native member credentials S-->>P: HttpOnly cookie and short-lived widget JWT P->>W: Official inline embed with userJwt callback W->>N: Authenticate the same external player ID P->>S: Ordered gameplay actions S->>S: Validate and commit state plus event outbox S->>N: POST events/claim with signed event proof N->>N: Evaluate achievement criteria W->>N: Read achievement and claim available reward N-->>W: Persisted points, XP and collectibles ``` Keep responsibilities explicit: | Layer | Owns | | - | - | | Three.js scene | Movement, interactions, animation and input | | Game server | Player identity, sequence, geometry/timing validation and delivery retries | | Nudj | Native challenge progress, achievement qualification, points, XP and reward ownership | | Official widget | The player's native loyalty interface | Do not award points locally and again through Nudj for the same activity. Native quiz completion and check-ins remain native actions. Custom game events cover activities such as walking, racing and finding treasure. ## Keep the three tokens separate | Token | Purpose | Where it belongs | | - | - | - | | Widget identity JWT | Asserts your authenticated external player ID to Nudj | Signed on your server; supplied to the widget through `userJwt` | | Native member access token | Authenticates Integration API calls for the internal Nudj member | Kept in the game's protected server session | | Signed event proof | Proves a specific validated gameplay milestone | Created and submitted by the game server | The game needs no Manager or MCP bearer token at runtime. Use administrative credentials separately when configuring your community, achievements and rewards. ### Establish one player identity For an existing product, use your authenticated account ID. Planet Nudj instead creates an opaque `planet-` on the server and keeps it in an encrypted, HttpOnly, Secure, SameSite=Lax cookie. It never accepts a browser-supplied member ID, email or points balance. Cookie deletion starts a new demo player; this is not cross-device account recovery. Create a dedicated signing key in your organisation's API configuration and store it in your server environment. This Node.js example signs the widget identity with the same standard-library mechanism used by the game: ```ts theme={null} import { createHmac } from "node:crypto"; const keyId = process.env.NUDJ_SIGNING_KEY_ID; const key = process.env.NUDJ_SIGNING_KEY; if (!keyId || !key) throw new Error("Configure Nudj signing credentials"); function sign(payload: Record) { const encode = (value: unknown) => Buffer.from(JSON.stringify(value)).toString("base64url"); const input = encode({ alg: "HS256", typ: "JWT", kid: keyId }) + "." + encode(payload); return input + "." + createHmac("sha256", key) .update(input).digest("base64url"); } // externalPlayerId comes from your verified server session. const now = Math.floor(Date.now() / 1000); const userJwt = sign({ sub: externalPlayerId, username: "Planet explorer", locale: "en", iat: now, exp: now + 300, }); ``` Use `kid` in the header and `sub` for the external ID. The current embed identity flow accepts a maximum lifetime of 600 seconds; this game uses 300. A one-hour legacy API Link example is not suitable for the widget identity flow. ### Obtain the native Integration API session The widget identity JWT is not an Integration API member access token. The game's server adapter establishes the native session through API Link: 1. Request `/api/link` on the configured Nudj **user-app origin**, with `userToken`, `clientId` and `callbackPath` query parameters. 2. Preserve Set-Cookie values in an isolated jar for this player. Follow a bounded number of redirects, checking every destination against the configured origin. 3. Request `/api/auth/session` on that origin using the same jar. Read `user.id` and `user.accessToken` without logging the response. 4. Call Integration `/me?communityId=...` with the native member token. Verify `id`, `externalUserId` and `organisationId` against your expected player and organisation before accepting the session. Build the link with URLSearchParams so the nested callback is encoded correctly: ```ts theme={null} const link = new URL("/api/link", userOrigin); link.search = new URLSearchParams({ userToken: userJwt, clientId: keyId, callbackPath: "/widget/planet-nudj?widget=1", }).toString(); // Fetch server-side with the isolated cookie jar. Never log this URL. ``` `/api/link` and `/api/auth/session` are user-app routes, not Integration API routes. This is the server bootstrap used by this example, not a dedicated OAuth token-exchange endpoint. See [API Link authentication](/developer/api-link-user-authentication) for the wider sign-in flow. The demo encrypts native credentials with AES-256-GCM inside its HttpOnly cookie. The session endpoint returns only public member fields and a fresh widget JWT. When the native token expires, bootstrap again using the same external ID. Coalesce the game and phone's first session request; otherwise simultaneous first loads can create different players. The demo also serialises bootstrap across tabs with the browser's Web Locks API where available. ## Put the official widget inside the game Use the official loader's inline mode. Give its container a real width and height, and create the container before loading the script: ```html theme={null}
``` ```js theme={null} window.nudjSettings = { community: "planet-nudj", trigger: "inline", container: "#planet-nudj-widget", autoOpen: false, userJwt: async () => { const response = await fetch("/api/planet-nudj/session", { credentials: "same-origin", cache: "no-store", }); if (!response.ok) throw new Error("Unable to connect rewards"); return (await response.json()).userJwt; }, }; // Append the configured user origin's /embed.js script after these settings. ``` Planet Nudj uses `https://derek-demos.nudj.cx/embed.js`. Use the user origin configured for your organisation. The parent game's origin must be allowlisted in the community embed settings. Inline mode creates one iframe in the container, with no floating action button or overlay panel. Do not create a floating widget and reparent its injected `#nudj-embed-fab` or `#nudj-embed-panel` elements into your game. In React, initialise after the container ref is mounted, retain the instance while the phone is closed, and call `window.nudj("destroy")` on disposal. The game obtains a valid session before appending the loader, because a failed `userJwt` callback can otherwise lead to guest recovery. It displays a retry state when session bootstrap fails. The supported public views are: ```js theme={null} window.nudj("navigate", "home"); window.nudj("navigate", "earn"); window.nudj("navigate", "spend"); window.nudj("navigate", "achievements"); window.nudj("navigate", "leaderboard"); ``` There is no public challenge-ID or action-ID navigation command in the version tested. Admin preview panel commands are not a merchant API. Keep task hints outside the iframe rather than trying to change its cross-origin DOM. If you listen for `nudj-widget-ready` or `nudj-widget-close`, validate both `event.origin` and `event.source === iframe.contentWindow`. Do not treat a message from another frame as authentication or achievement proof. ### Match the widget's gift count The header's gift badge counts claimable, unclaimed achievements. It is not an inbox unread count or a count of owned rewards. The widget posts the same value to its parent as `{ type: "nudj-unread-count", count }` when it changes. Reuse your existing message listener and current iframe reference: ```ts theme={null} if (event.origin !== widgetOrigin || event.source !== iframe?.contentWindow) return; const data = event.data; if ( data?.type === "nudj-unread-count" && Number.isSafeInteger(data.count) && data.count >= 0 ) { setClaimableCount(data.count); } ``` This is display data, never authority to grant a reward. Register the listener before loading the iframe and remove it on disposal. Accept updates down to zero after claims. Before the initial authenticated count read completes, its value is unknown; a read failure must not manufacture a zero. The world launcher loads its initial count through the existing authenticated `GET /api/planet-nudj/game?notifications=1`. This keeps the widget iframe lazy: an eager iframe can stop at cookie choice before it emits a count. The equivalent native read used by the server is `GET /api/v2/integration/achievements?communityId=&status=live&limit=50&skip=0` with the existing member headers. Count edges where `activeTier.userState.isClaimable` is true and `isClaimed` is false. That is the widget header's current query and predicate. Its 50-item page covers Planet Nudj's 14 regular achievements; larger catalogues need pagination and a matching widget fix before promising an accurate total. Refresh on initial load, phone close and confirmed native progress, with no interval polling. The native count lookup is read-only; the reused game GET retains its existing deferred outbox delivery. Abort obsolete requests and use a version counter so an older read cannot overwrite a newer iframe message. The live test at website commit `8807a9d9` reused an isolated player's existing First wander qualification. The closed launcher showed one before the iframe opened. After Required-only consent, the native gift badge also showed one. An actual pointer claim succeeded; closing the phone refreshed the count to zero and hid the launcher badge. No member or game-store reset was used. | Before the claim | After the claim | | - | - | | World launcher with one claimable achievement | World launcher after the claim with the count badge hidden | The exact result, live log and screenshots are in `output/planet-audit/launcher-notifications`. Fixture checks at 1440 and 390 pixels cover invalid counts, wrong origins/frames, 99+ display, zero/unknown states, accessibility and the single lazy iframe. These are live-preview results, not verification of a production website deployment. The loader documents `nudj:unread_changed` on `document` and `nudj("on", "unread_changed", callback)`, but the deployed loader suppresses these in inline mode. Emission currently depends on a closed panel and an existing floating badge, tracked as NJ-2574. The validated iframe message above is the supported protocol path available to an inline host today. ## Open Lite views as the same player The passport annex opens four native Lite views from the character's phone. Mono shows the existing welcome challenge on its own. Journey, Stamp card and Calendar show the community's challenges. These views reuse native progress; changing the layout must not create another completion or reward. Planet Nudj phone showing the passport annex, its Mono format card and a button to open the native view The game uses a same-origin handoff instead of relying on whichever Nudj account the browser last used: ```html theme={null} Open the Mono view ``` The route accepts only `mono`, `journey`, `stamp` or `calendar`. It reuses the existing game-session handler to verify or renew the native member, obtains a fresh five-minute identity JWT, and returns a 303 through API Link. Its fixed callbacks are `/lite/mono/{welcomeChallengeId}` and `/lite/{journey|stamp|calendar}/{communitySlug}` on the configured Nudj user host. Copy the renewed game cookie onto that response and set `Cache-Control: private, no-store` and `Referrer-Policy: no-referrer`. Never log the redirect's `Location` header: it contains the short-lived identity proof. The implementation added 36 route lines and a 41-line focused check on 10 September 2026. Run `node scripts/check-planet-lite.cjs` in the website checkout. Live tests used four separate browser contexts with only the game's cookie and no Nudj cookies. Every format opened as the same member, retaining ten points, thirty XP and one completed welcome challenge. A separate test clicked all four links from the phone and confirmed the same identity and an empty referrer. Invalid formats returned 400; cross-site requests returned 403. The four Lite variants were already enabled for this community. No challenge duplication or `enabledForSingleView` update was needed. Native presentation defects remain tracked: Stamp's hard-coded Pampers cash graphic, Mono's completed attempt and badge counters, and Journey's mobile heading overlap. Their presence does not mean the underlying member progress was lost or awarded twice. ## Validate gameplay before sending an event The game posts a small envelope to its own server: ```ts theme={null} await fetch("/api/planet-nudj/game", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sequence: 12, action: { type: "lake-jump" } }), }); ``` The server rejects cross-site requests, bodies over 2 KB, unknown action fields, out-of-order sequences, unsupported IDs and caller-supplied award amounts. It checks movement against server time and shared world geometry. Six ordered landings qualify island arrival; digging at the island qualifies a separate treasure milestone. Guided accessibility input uses the same native rewards. State and its event outbox commit in one SQLite transaction. Send native HTTP requests after committing, then mark delivered events. The browser serialises actions, coalesces unsent movement samples and retries the same sequence and payload if a response is lost. Network delays can compress the interval between two valid actions arriving at the server. For a transition that arrives before its minimum duration, the game returns `425` and `retryAfterMs` without consuming its sequence. Retry the same envelope after that delay. Invalid order or an expired run still returns `409`. These are the example game's response conventions, not Nudj API status rules. ### Validate each puzzle input The six camp games became interactive puzzles in website commit `1bf3b9c4`. Each normal game uses the objects in the 3D world. Guided mode renders a playable graphical version of the same mechanism and uses the same server rules. | Camp | Player interaction | Native event | | - | - | - | | Nib's clouds | Rotate and fit a rung, then hold upward input to climb | `planet.cloud.climbed` | | Sphinx | Align the carved rings with the displayed symbols | `planet.pyramid.opened` | | Wisp's cove | Collect two pieces of moss and play a three-note instrument | `planet.trail.completed` | | Ideas lab | Connect six tubes, launch the draft and earn the approval stamp | `planet.draft.approved` | | Storefront relay | Rotate six cable tiles to restore the route | `planet.relay.restored` | | Helpful tower | Place four weights on hooks to balance the mechanism | `planet.tower.opened` | For example, the game sends a cable rotation through its existing endpoint: ```ts theme={null} await fetch("/api/planet-nudj/game", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sequence: 12, // The next sequence for this game session. action: { type: "quest-play", control: "rotate-0" }, }), }); ``` In `lib/planet-nudj/game.ts`, lines 144–168 at that commit, the server checks the active mechanism, its allowed controls, normal-mode proximity and minimum input interval before applying the move. `quest-puzzles.ts` evaluates pipe connections, the note sequence, weight torque or symbol alignment. Only a solved mechanism advances progress. The old `quest-step` action is rejected at a puzzle stage, so it cannot bypass the controls. The existing outbox, stable nonce and native achievement claim still own the once-only reward. Actual mobile 3D relay with six rotatable cable tiles and Back to world control Actual guided mobile relay showing six cable tiles, inlet and outlet labels Version migration restarts incomplete older runs at the new definition. Completed native rewards and their event records remain intact. Definition versions are two for cloud, pyramid, lab, relay and tower, and three for Wisp. A completed cosmetic preview still needs an explicit new playthrough before its first native award; pending or delivered reward events prevent another rewarded replay. The saved results contain six normal 3D runs, six graphical guided runs and one additional mobile 3D relay run. Each earned ten points and ten XP through a native claim, survived reload and rejected a duplicate claim with `400`. The normal tests clicked the objects; Nib also used held climb input. Mobile checks used browser-emulated touch, not a physical handset. Raw logs, emitted result JSON and screenshots are in `output/planet-audit/puzzle-games`. These results concern the preview connected to Nudj, not a production website deployment. ### Sign and submit the native event proof After validation, reuse the server-side `sign` function from the identity example: ```ts theme={null} const now = Math.floor(Date.now() / 1000); const eventName = "planet.island.arrived"; const milestone = "first-island"; const eventToken = sign({ organisationId, communityId, payload: { name: eventName, nonce: `planet:v1:${communityId}:${member.id}:${eventName}:${milestone}`, schemaVersion: 1, landings: 6, assisted: false, }, expiresAt: (now + 300) * 1000, isSingleUse: true, clientSecretId: keyId, iat: now, exp: now + 300, }); const response = await fetch(integrationBase + "/events/claim", { method: "POST", headers: { "Content-Type": "application/json", "x-user-access-token": "Bearer " + member.accessToken, "x-api-domain": integrationBase, }, body: JSON.stringify({ eventToken }), redirect: "error", signal: AbortSignal.timeout(25_000), }); if (!response.ok) throw new Error(`Nudj event claim failed: ${response.status}`); ``` `integrationBase` ends in `/api/v2/integration`. Keep it in trusted server configuration. The token's `expiresAt` is milliseconds; JWT `iat` and `exp` are seconds. The nonce stays stable for this member, event and milestone across retries and reloads. Generating a new nonce for each request defeats duplicate protection. Never return signed event proofs to the game client. Configure a native achievement to listen to the exact custom event name. For the island example, use `planet.island.arrived` with custom category and subcategory. For native quiz completion, use `challenge_challenge-completion` and filter `eventSourceId` to the actual quiz challenge ID. ## Register and test custom-event schemas ### Configure the achievement separately A schema validates an event's payload. An achievement defines the qualification and reward. Create the achievement through `POST /achievements` on the Admin base with explicit queries and a matching tier criterion: ```json theme={null} { "communityId": "", "status": "draft", "progressPeriod": "open", "details": { "title": "Reviewed and approved" }, "queries": [{ "type": "event", "alias": "reviewed_and_approved", "isCommunityScoped": true, "parameters": { "eventName": "planet.draft.approved", "eventCategory": "custom", "eventSubCategory": "custom" } }], "tiers": [{ "tierNumber": 1, "details": { "name": "Reviewed and approved" }, "criteria": { "type": "count", "parameters": { "query": "reviewed_and_approved", "operator": ">=", "value": 1 } }, "rewardDistribution": null }] } ``` Then call `POST /achievements/{id}/distribution`: ```json theme={null} { "tierNumber": 1, "pointsToDistribute": 10, "bonusXpToDistribute": 10, "rewardsConfig": { "mechanism": "all", "amountToDistribute": 0, "allocations": [] } } ``` The distribution endpoint creates and links its event ID. Read the achievement back and verify the exact event name, matching alias, open period, draft state and persisted ten-point/ten-XP distribution. For an existing achievement, updates use `POST /achievements/{id}`, unlike event-schema updates, which use PATCH. The MCP convenience mapper still rewrote `planet.draft.approved` to `custom_custom` in this test, tracked as NJ-2534. Explicit Admin queries preserved the correct name. Do not infer success from a create response without read-back. On 10 September, the owner approved publishing ready content. The lab, relay and tower achievements were then changed to live with status-only Admin updates. Read-back confirmed their exact queries, artwork and ten-point/ten-XP distributions were preserved. The API added null defaults to optional tier detail fields; this did not change the rules or rewards. Publish the native achievement before enabling its game mapping. Both the game queue and signer must reject disabled rewards. An ID alone does not activate a draft. Earlier preview completions must not silently become reward events: offer an explicit new playthrough for the live reward, and continue rejecting replays once its native event has been recorded. The publication checks, before the physical-puzzle redesign, passed normal and guided runs for all three activated camps with their real ordered steps, native ten-point/ten-XP claims, reload and duplicate-claim rejection. An existing player who had finished the lab preview received nothing on load or reload after activation. Choosing "Play again to earn the reward" started at step zero; completing all six steps then qualified and paid once. No member or game-store reset was used. The raw test records and screenshots are saved under `output/planet-audit/activated-camps` in the website checkout. ### Register the payload schema The Admin API can create and publish the same event schemas shown under **Organisation settings → Event schemas**. Schema setup is organisation-scoped; keep the administrative credential on your server or in protected tooling. The game continues to use its member token and signed event proof. ### Find your schemas in the admin Select your organisation, then open **Organisation settings → Developer → Event Schemas**. For this demo, select **Derek Demos** and open the [schema list](https://admin.nudj.cx/admin/settings/organisation/event-schemas). The URL ending in `/event-schemas/new` opens an empty creation form. It does not list definitions you have already created. Live Nudj admin list showing published walking, island-arrival and treasure schemas with valid and invalid event counts These screenshots show the live demo in dark mode on 10 September 2026. Activity counts change as players explore the game. ### Create and publish through the API | Method and path, relative to the Admin base | Purpose | | - | - | | `GET /event-schemas` | List definitions before creating duplicates | | `POST /event-schemas` | Create a draft definition | | `PATCH /event-schemas/{id}` | Update properties or publish it | | `POST /event-schemas/validate` | Test a payload without storing an event | | `GET /event-schemas/{id}/events` | Inspect real events and their validation issues | | `GET /event-schemas/activity-summary?sinceHours=1` | Compare recent valid and invalid event counts | Create a draft for the exact event name your game emits: ```json theme={null} { "eventName": "planet.island.arrived", "description": "The player completed the six ordered island landings.", "properties": [ { "name": "landings", "type": "number", "required": true, "integer": true, "min": 6, "max": 6 }, { "name": "assisted", "type": "boolean", "required": true }, { "name": "nonce", "type": "string", "required": true, "minLength": 1, "maxLength": 240 }, { "name": "schemaVersion", "type": "number", "required": true, "integer": true, "min": 1, "max": 1 } ] } ``` The create response returns its ID and `state: "draft"`. Use that ID to publish with `PATCH /event-schemas/{id}` and body `{ "state": "published" }` after testing. The event name cannot be changed through that partial update. Do not define a property called `name`: it is reserved for the event name and is removed before property validation. Unknown additional payload properties are currently allowed. Open a schema and expand a property to inspect its type and constraints. The island event requires an integer `landings` value of exactly six, so both its minimum and maximum are six. `assisted` is a required boolean: both `true` and `false` qualify, covering guided and manual play. Published planet.island.arrived schema with landings expanded, showing minimum six, maximum six, Integer only and Required enabled ### Test a payload and inspect real events ```json theme={null} { "eventName": "planet.island.arrived", "payload": { "name": "planet.island.arrived", "landings": 6, "assisted": true, "nonce": "dry-run-example", "schemaVersion": 1 } } ``` Send that body to `/event-schemas/validate`. Valid results have `valid: true`, `schemaExists: true` and no issues. A string such as `"yes"` for `assisted` or five landings returns field issues. Dry-run validation writes no event. Planet Nudj now has published definitions for `planet.walk.milestone`, `planet.island.arrived` and `planet.treasure.found`. They drive First wander, Against the current and X marks your spot through the existing exact-name achievement queries. No second set of achievements or duplicate rewards is needed. The game payload needs no new schema ID; lookup uses its event name within the organisation. | Event name | Required gameplay properties | Native achievement | | - | - | - | | `planet.walk.milestone` | `distance`: number, minimum 18 | First wander | | `planet.island.arrived` | `landings`: integer, exactly 6; `assisted`: boolean | Against the current | | `planet.treasure.found` | `assisted`: boolean | X marks your spot | All three also require `nonce`, a string of 1–240 characters, and `schemaVersion`, an integer equal to one. These checks validate the payload shape. Your game server must still verify that the player actually performed the activity. In the admin, **Test a payload → Validate** performs the same dry run. The **Live events** panel shows stored ingress. Expand a row to compare its received payload with its validation result. Use **Valid** or **Invalid** to filter it, or pass `isValid=true` or `isValid=false` to `GET /event-schemas/{id}/events`. Expanded valid island-arrival game event alongside a successful dry-run validation with landings six and assisted true Invalid island event showing landings must be at least six and assisted must be a boolean, with matching dry-run field errors Actual keyboard movement and a fresh guided island run produced events with the correct `validation.schemaId` and `validation.isValid: true` in the live schema streams. Their native achievement claims paid 20/20 for walking and a combined 30/30 plus the key for island arrival and treasure. Missing, out-of-range and wrong-type payloads were rejected in the test endpoint; actual signed invalid submissions were retained as invalid diagnostic rows and awarded nothing. There is an open ingress error-contract defect, tracked internally as NJ-2556: a published-schema rejection returns 500 from `/integration/events/claim`, and retrying its already rejected nonce returns 200 even though the stored event remains invalid. The admin screenshot's HTTP 400 help text does not describe this signed-claim failure correctly. Check native validation and achievement state when diagnosing this case. Do not credit rewards or manufacture a new nonce to turn a rejected payload into a successful milestone. This differs from replaying an originally valid event, which is an intended successful no-op. ## Read progress and claim native rewards All Integration calls below use the native member headers shown above: | Method and path, relative to Integration base | Purpose | | - | - | | `GET /me?communityId=...` | Verify identity and read balance/XP | | `POST /events/claim` | Submit the signed event proof | | `GET /achievements/{id}` | Read native qualification and claim state | | `POST /achievements/{id}/claim` with `{}` | Claim an earned achievement | | `GET /me/badges?communityId=...&limit=100&skip=0` | Read collectible badge ownership | | `GET /me/rewards?communityId=...&limit=100&skip=0` | Read ordinary reward assets, such as a purchased pass | | `GET /challenges/{id}` | Read native challenge completion prerequisites | Event processing is asynchronous. A successful event POST does not mean the achievement is already claimable. Poll with a bounded delay and read: ```ts theme={null} achievement.userState.isComplete; achievement.activeTier.userState.isClaimable; achievement.activeTier.userState.isClaimed; achievement.activeTier.userState.earnedPoints; achievement.activeTier.userState.earnedXp; ``` After claiming, re-read the native member balance and ownership. Repeating an already-completed event claim returned `200` without another payout in testing. Repeating an achievement claim returned `400`; reconcile its native claimed state before deciding whether a retry succeeded. Do not swallow every `400` as success. Badges and ordinary rewards are separate collections. The Secret island key is a badge and appears in `/me/badges`. `/me/rewards` intentionally excludes it. The Planet explorer pass is an ordinary reward asset. Follow `totalCount` and `edges` pagination, and check member, community and expiry when using ownership to unlock gameplay. Creating a reward does not place it in the shop; it needs a shop allocation. ## Implementation size and checks The website's `1bf3b9c4` commit contains 868 physical lines across the eleven selected integration, puzzle-rule and puzzle-interface files below, measured on 10 September 2026 by counting newlines in `git show` output. This includes blank lines and comments. It excludes the 3D scene and mechanism meshes, other game files, tests, artwork and community configuration; it is not the total game size. Measuring the commit keeps concurrent uncommitted UI changes out of this snapshot. | Website path | Lines | | - | -: | | `lib/planet-nudj/nudj-api.ts` | 225 | | `lib/planet-nudj/session.ts` | 45 | | `lib/planet-nudj/client-session.ts` | 26 | | `app/api/planet-nudj/session/route.ts` | 54 | | `app/concepts/little-planet/real-widget.tsx` | 122 | | `lib/planet-nudj/game.ts` | 181 | | `lib/planet-nudj/game-store.ts` | 21 | | `app/api/planet-nudj/game/route.ts` | 43 | | `app/concepts/little-planet/game-events.ts` | 31 | | `app/concepts/little-planet/quest-puzzles.ts` | 54 | | `app/concepts/little-planet/quest-hud.tsx` | 66 | Run the focused checks in the website repository: ```bash theme={null} node scripts/check-planet-nudj-api.cjs node scripts/check-planet-nudj-session.cjs node scripts/check-planet-game.cjs node scripts/check-planet-game-client.cjs # Protected credentials are read from a file, never passed as token arguments. PLANET_NUDJ_ENV_FILE=/protected/path/server.env \ node scripts/check-planet-game-live.cjs PLANET_NUDJ_ENV_FILE=/protected/path/server.env \ node scripts/check-planet-game-live.cjs --guided ``` Live standard and mobile guided island runs proved actual game input, all six landings, digging, two native achievement completions, a combined claim delta of 30 points and 30 XP, and key ownership. Reload preserved member and balance. Repeat claims, another member's unearned claims, forged input and cross-site requests were rejected. Those runs claimed through the Integration API; they do not by themselves prove the widget's claim buttons. The original cloud, pyramid and six-step Wisp quests also have six recorded normal/guided passes: each qualified, claimed 10 points and 10 XP, survived reload and rejected a duplicate claim. A fresh production read confirms all six exact achievement records remain complete and claimed for those amounts. Their screenshot index is retrospective because the original runs did not save standalone result JSON; the fresh database read is stored separately. The new lab, relay and tower rewards were subsequently published after owner approval; their activation checks are separate from these earlier original-quest runs. An independent live phone check confirmed that the iframe and game session resolved the same native member, with one inline iframe and no floating launcher. Keep UI claims, keyboard/touch paths and other activities in the test record as they are checked; a passing island run is not proof of every game path. ## Limits and integration pitfalls | Observation | What to do | | - | - | | Newly generated signing key returned `kid_unknown` while configuration was cached | Verify the key and environment first; the observed demo required a platform-configuration cache refresh. Never expose the secret while debugging. | | Localhost was not allowlisted for the live community | Test on the configured allowed origin. Do not spoof Origin to work around embed validation. | | Day-one streak payouts can recur after a broken streak is rebuilt | Use a separate once-ever achievement for a one-time onboarding bonus. | | A phone refresh can retain stale native progress | Reconcile native state and refresh the official instance when external gameplay delivers an event. | | Node exists by absolute path but Turbopack workers cannot find it | Put the selected Node bin directory on PATH before starting Next.js. | | Passing Node `--env-file` through this Next dev launch failed | Source the protected environment in the launch shell; do not propagate `--env-file` into NODE\_OPTIONS. | The example uses Node 22's built-in SQLite in WAL mode on one persistent preview host. Use shared transactional storage for multiple application instances, or deliberately provision one persistent host. An ephemeral serverless filesystem does not provide this persistence guarantee. Movement validation bounds browser telemetry using geometry and server time. A scripted client that follows those constraints can still play. This is not an authoritative multiplayer physics server, and it must not prove real purchases or other high-value commerce. Keep sample orders and receipt demonstrations out of genuine purchase events. ## Open issues found by the main-journey test These failures remain separate from the passing island and race tests. Do not use the transient widget display as proof of persisted state. **Inline check-in scope.** A real widget check-in wrote the correct community ID in `payload.communityId` and `eventSourceId`, but omitted top-level `communityId`. The widget briefly displayed one day; a native `/me/streaks` read still showed zero, and the community-scoped achievement remained locked. The shared event logger took community scope from a navigation cookie that was absent in this inline session. A source fix is being prepared to pass the known target explicitly. For the example's first-check-in achievement, a temporary query selects the exact community as `eventSourceId`, keeps native category/subcategory/name and preserves member and organisation scope, without requiring the missing top-level field. Changing this query did not recalculate existing saved progress on read. It also does not repair the durable streak or an event carrying a different, incorrect top-level community ID. Do not generate a replacement nonce to force a payout. **Challenge points and XP.** The native welcome challenge completed and awarded 20 XP plus its collectible, but zero points despite a configured 20-point reward. With fixed points distribution disabled, challenge points are multiplied by the player's action-XP ratio. In this example the action explicitly awards zero XP, while the ratio denominator uses the organisation's 50-XP action default. Completion bonus XP does not correct that ratio. Validate actual awarded points for your configured XP policy; do not change organisation-wide defaults to repair a single demo. The welcome, quiz, check-in, pass and factory journey is not yet verified end to end. These native issues need resolution and another live run before release. ## Record a game check-in through a custom event The older integration used a custom event named `check-in`. A historical migration converted it to native `community / check-in`. Current `POST /integration/events/claim` emits `custom / custom` and preserves the exact `payload.name`; adding a category parameter cannot turn it into a native event. You can still configure a streak to accept your game's own check-in event: ```json theme={null} { "triggerEvents": { "platform": ["check-in"], "custom": ["planet.check-in"] } } ``` Preserve the full existing trigger set when updating it. The server sends the signed custom event only after the player's explicit check-in action. This example uses a `check-in-YYYY-MM-DD` milestone in the verified organisation's Europe/Berlin timezone, not the player's browser timezone. The signed payload contains the same day and timezone, and the signer rejects mismatches. Cap proof expiry at organisation midnight. Native event time is its ingestion time, so an old check-in must not be replayed tomorrow as if it happened today. The example retains expired outbox entries with a reason and asks the player to check in again. Verify native `/me/streaks` after delivery, including repeated clicks, native and custom events on one day, and date boundaries. The live game API path produced a properly scoped event, a durable one-day streak and a claimable first-check-in achievement. Same-day repetition left one delivered daily event. Claiming through the widget handler added 30 points and 30 XP, and reload preserved them. The repeatable browser check now covers the actual game button, cookie consent, scaled pointer claim, native read-back and reload for a fresh player. The same test player's native quiz was completed through all three widget questions. Invoking the real widget claim handler produced a200 Integration achievement claim and increased points by20 and XP by20; native state showed claimed. The later repeatable phone test also verified the actual pointer path. The inline loader scales its iframe; map pointer coordinates through the frame rectangle and its internal viewport after scroll and animation settle. The API streak date read also exposed a timezone defect: a check-in at 22:21 UTC was already September 10 in the configured Europe/Berlin timezone, but `recentCompletedDates` reported September 9. The single and batch helpers use server-local day comparisons and UTC date slicing. This is tracked for repair; keep the game calendar aligned to the organisation rather than copying the incorrect API timeline date. ## Native game results and reward configuration The native memory game exposed a zero-value default error. With three pairs worth ten score each, `timeBonus: 0` and a 30–30 reward band, a completed round scored 73 because the game replaced zero with a multiplier of one. Native read-back confirmed completion with zero points and XP, since 73 missed the configured band. Treat a saved game configuration and its rendered result as separate checks. For the example, explicitly enabling a one-point-per-second bonus and assigning a 30–120 completed-round band preserved the intended ten-point/ten-XP reward. A fresh live test then completed the nested game, submitted score 117 and received ten points and ten XP. The source correction for explicit zero remains pending release; a configuration workaround does not prove that source bug is fixed. When replacing game rewards through MCP, supply both the score conversion and the distribution bands in the same assignment, then read them back: ```json theme={null} { "operation": "assign_distribution", "gameConfigId": "", "scoreConversion": { "pointsPerScore": 0, "xpPerScore": 0 }, "distributions": [ { "minScore": 30, "maxScore": 120, "pointsToDistribute": 10, "bonusXpToDistribute": 10 } ] } ``` This operation replaces the reward configuration. Sending only bands cleared the existing conversion; a conversion-only nested update cleared the bands. A top-level `scoreConversion` on `games.update` was ignored by its mapper. Use the supported full assignment shape while that contract is being clarified. ## Referrals, birthdays and boosts ### Referrals require an accepted code and qualification Referral setup has separate rewards for the referrer and the invited player, plus a backing achievement defining what the invited player must do. In Planet Nudj, each side receives ten points and ten XP after one native challenge completion. Merely opening a link or accepting its code does not earn that award. Use the native member token for these Integration API calls: | Method and path | Input or purpose | | - | - | | `POST /me/referral/codes` | `{ "communityId": "" }` creates or returns the player's code | | `GET /me/referral/codes?communityId=...&limit=100&skip=0` | Read codes, `usedByCount` and each entry's `conditionsMet` | | `POST /me/referral/accept` | `{ "code": "" }` accepts the code for the current player | | `GET /me/referral/status?communityId=...` | Read `referredBy` and confirm the accepted relationship | Two isolated game players verified the native flow. After code acceptance, the invited player completed the welcome challenge through the real phone. Each side received ten referral points and ten XP. The code recorded one use with `conditionsMet: true`; repeated acceptance returned 200 without another payout. The welcome challenge's separate zero-point defect remained visible in this test. Two integration gaps remain. New code creation succeeds, but the widget does not refresh its code query until reload, tracked as NJ-2558. The game's incoming `referralCode` URL parameter also needs a server handoff. The tested acceptance API infers the community from the code and cannot enforce the caller's expected community before writing. NJ-2559 adds that check. Do not put an Admin bearer in the game or rely on a check after acceptance to repair a wrong-community write. ### Add giveaway entries to a referral reward Giveaway prize supply and entry supply are separate. The Christmas AirPods 5 draft uses one prize and an unlimited entry pool. The relevant Admin reward fields are: ```json theme={null} { "status": "draft", "allocationsType": "entries-only", "assetsSupply": 1, "entriesSupply": "Infinity", "isSharedAssetsSupply": true, "isSharedEntriesSupply": true, "maxAssetsPerUser": 1, "entryPointsPrice": 0, "isManualGiveaway": false } ``` Create the complete reward through `POST /api/v2/admin/rewards` and read it back before attaching it. At launch, the proposed referral package adds an allocation with `allocationType: "entries"`, the giveaway's `rewardId` and `amountToDistribute: 1` to the **referrer** side. Preserve the existing points and XP fields. One newly qualified friend should produce one entry; a share-button click alone does not establish a referral. This AirPods reward remains draft with no allocation attached. Its proposed Christmas Day 2026 draw is not a public launch announcement. Participant eligibility, real identity checks, isolated entry-award tests, the game arrival handoff and physical prize fulfilment still need completing before launch. Draft status does not schedule the automatic giveaway job. The MCP `reward_giveaways` tool currently hard-codes unlimited prize supply and omits draw-mode controls, tracked as NJ-2571. Use the supported Admin API fields to configure one prize. Reward `tags` and `campaigns` accept existing entity IDs, not new text labels; invalid labels currently cause an ObjectId HTTP500, NJ-2570. Omitting optional tags allowed the draft to be created successfully. ### Birthdays follow the real calendar The native `question-birthday` action writes `birthDay`, `birthMonth` and `birthYear` to the member profile. An isolated player entered 31 December 2000 through the phone. `/me` and a reload returned the same date; the widget marked the activity completed and disabled its button. Points and XP did not increase. Planet Nudj's birthday reward is configured for twenty points and twenty XP on the birthday, with zero advance days, a one-day membership minimum and the date locked after entry. This proves date capture and no early award. It does not prove a future scheduled payout; keep that outcome pending until its real date. ### Verify boost expiry with another earned reward The live one-hour points and XP boosts doubled a ten-point/ten-XP treasure claim to twenty of each. After both effects actually expired, the same player walked 19.7 world units and qualified for First wander. Its eventual claim paid the base twenty points and twenty XP, and native `communityData[].effects` was empty. The first post-expiry claim returned 500. Native read-back showed no award and an unclaimed achievement, then an identical retry succeeded once. Better Stack recorded a MongoDB write conflict during points distribution, tracked as NJ-2560. The points and XP helpers launch expired-effect cleanup outside the reward transaction, a suspected race that still needs repair. An expired entry remaining in a read response is not itself proof of an active multiplier: cleanup is lazy. ## Distinguish visible tier progress from persisted promotion The configured tiers use lifetime points earned: New neighbour at zero, Explorer at 80 and Maker at 140. A real guided cloud quest and phone claim increased the main player's lifetime points from 130 to 140. The widget showed Maker with 80 spendable points. The player then bought another 60-point pass through the phone's quantity and confirmation screens. Available points fell to 20, while lifetime points stayed at 140, XP stayed at 200 and Maker remained. Reload preserved the result and both purchased pass assets. A duplicate quest claim returned `400`. Actual Planet Nudj widget header showing Maker tier and 20 spendable points after a reward purchase The database read before that purchase confirmed 80 available points, 140 lifetime points and 200 XP, but no `loyaltyTierState`. The widget derives the visible label from lifetime earnings. This is the stored-promotion defect tracked as NJ-2554. These demo tiers have 1x multipliers and no entry reward configured; this run does not prove promotion history or an entry payout. ## Purchase and factory proof The same player completed the welcome and quiz, checked in, then earned an optional island achievement. With 70 legitimately earned points, the player bought the 60-point explorer pass through the native shop. Available points fell to 10 while lifetime points stayed 70 and XP stayed 120. `/me/rewards` returned the pass asset for that member and community. The server then confirmed all factory prerequisites. The player entered an idea and ran the reduced-motion factory through its real timed game endpoint. The native Programme maker achievement completed, and its pointer claim added 20 points and 20 XP. Reload preserved the pass, factory milestone and balances. This proves the downstream purchase and factory contract. The advertised main-only welcome/quiz earnings still require the challenge-XP repair described above; optional earnings do not remove that release hold. The same normal-factory check has a strict `--main-only` mode. It skips optional Curious mind and First wander claims, then checks every native balance: | Stage | Available points | Lifetime points | XP | | - | -: | -: | -: | | Welcome complete | 20 | 20 | 20 | | Quiz complete | 50 | 50 | 50 | | First check-in claimed | 80 | 80 | 80 | | Explorer pass purchased | 20 | 80 | 80 | | Factory claimed | 40 | 100 | 100 | ```bash theme={null} PLANET_NUDJ_ENV_FILE=/protected/path/server.env \ PLAYWRIGHT_MODULE=/path/to/installed/playwright \ node scripts/check-planet-normal-factory.cjs --main-only ``` The pre-release run stopped at welcome with zero available points, zero lifetime points and 20 XP. It exited with failure before any optional claim. This is current evidence for NJ-2532. It does not prove the later stages; rerun after the repair is released. The script reports `mainOnlyFundingVerified: true` only after the whole sequence, reload and duplicate protection pass. Default mixed-funding mode always reports false for that field. Run the repeatable phone and native-game flow in the website repository: ```bash theme={null} PLAYWRIGHT_MODULE=/path/to/installed/playwright \ node scripts/check-planet-phone-live.cjs --memory ``` It creates an isolated test member and checks actual pointer input, same-member widget identity, check-in/claim persistence, duplicate rejection, no floating launcher, no extra phone header, and the native memory reward. It does not claim coverage of every station or future calendar outcome. ## Award discoveries after completing a lesson Secret seeker needs three different discoveries. The team, analytics and integration lessons each record `planet.discovery.completed` after the player finishes an activity: | Lesson | Completion | Event payload `discoveryId` | | - | - | - | | The right-key cabinet | Match three jobs to Viewer, Moderator and Manager, then finish the puzzle | `team` | | The control room | Identify points as the spendable balance | `analytics` | | The storefront loading bay | View both widget placements and confirm | `integrations` | Await delivery to Nudj before showing "Discovery saved to Nudj." If delivery fails, keep the activity's retry button available. Opening a lesson does not qualify. Art, receipt examples, Lite formats and disabled draft quests do not emit these discovery events. The right-key cabinet phone lesson after completing the role puzzle, showing Discovery saved to Nudj For normal walking, retain the server's station-proximity check. The accessible guided path records a server-side lesson start and requires the same lesson ID and at least 900 ms before completion. It preserves the player's physical position. Both paths use the same signed-event and nonce contract described above, so repeating a completed lesson creates no additional native event. The guided player's three completions produced exactly three scoped native events. A real pointer claim in the phone paid 20 points and 20 XP. Reload preserved the reward; repeating the integrations lesson kept the event count at three, and a duplicate achievement claim returned `400`. The same run verified fresh cookie consent, an unobstructed Back button and no extra phone header or floating launcher. Native Secret seeker reward confirmation in the game phone showing Claimed and plus 20 points Run the live guided check from the website repository: ```bash theme={null} PLAYWRIGHT_MODULE=/path/to/installed/playwright \ PLANET_AUDIT_DIR=/path/to/verification-output \ node scripts/check-planet-discoveries-live.cjs ``` Normal walking separately passed all three lesson completions, native event delivery and a real phone claim for 20 points and 20 XP. Reload retained the three discoveries and reward; a duplicate claim returned `400` with unchanged balances. The normal race and factory checks also passed with native claims and duplicate protection. The factory used optional walking earnings; the advertised main-only funding still waits for the challenge-points repair. ## Separate business journeys This section records the separate-business implementation. On 10 September 2026 the game direction returned to one Planet Nudj adventure: a visible business choice selects the starting shop only. Keep existing business communities and member history intact. The single-adventure opening and faster tour remain an implementation task, not a release claim. The 10 September 2026 extension gives eight fictional shops their own learning activities, puzzle achievements and wallets. You choose a business at the globe; switching saves the current journey first. Another shopkeeper can offer a switch, but walking past a shop must not change the active identity. Original Planet Nudj progress and its campaigns remain separate. The native content and artwork are verified drafts. All business enable flags, native embeds and daily streaks remain disabled. The website implementation is tracked in [website PR #233](https://github.com/Nudj-digital/nudj-website/pull/233). This chapter extends [the original guide PR #4433](https://github.com/Nudj-digital/monorepo/pull/4433); it does not establish that the new journeys work in production. ### Separate identities as well as communities Derek Demos has `pointsConfig.useOrgLevelPoints: true`. With that setting, one native member shares spendable points across communities. Root XP and some aggregate progress reads also span communities. You cannot isolate eight wallets just by creating eight communities or adding segmentation tags. Use a stable server-owned visitor × business identity and provision its native member when that journey is first selected. Omit a shared real email from these demo identity assertions: native API Link resolves by email as well as external ID, so reusing an email can resolve different persona IDs to the same account. Verified account linking needs its own migration and duplicate-reward checks. Keep the existing organisation signing key on the server. It is organisation scoped; a separate key per shop would change revocation granularity, not create a tenant boundary. Leave the organisation points setting and legacy player identity unchanged. This design separates normal demo journeys; it does not turn communities into independent security tenants. A read-only production check found `anonymous-login` and `anonymous-account-conversion` already enabled for Derek Demos. No flag changed. Integration `GET /feature-flags` with empty input reads organisation flags; `ff()` has no community selector, and community visibility has no override for these two flags. Deprecated `embedConfig.mode` affects snippet templates, not runtime authentication. These are prerequisites only; they do not prove the guest, account-linking or durable-progress paths tracked in NJ-2584. The server allowlist maps a business key to its community, challenges, rewards and event names. Never accept arbitrary browser member IDs, community IDs, reward IDs or award amounts. Pin pending requests and event retries to the member and business that created them. Key client caches by business, and clear the visible balance while the replacement widget identity is unresolved. This excerpt uses actual draft IDs from the website's `lib/planet-nudj/persona-content.json`. It is part of the server allowlist, not a complete manifest or permission to enable this journey: ```json theme={null} { "schemaVersion": 1, "timeZone": "Europe/Berlin", "personas": { "coffee": { "name": "Morrow Roast", "communityId": "6aa298e1ad3c6809b8ab1c40", "communitySlug": "planet-morrow-roast", "status": "draft", "enabled": false, "quests": { "dropped-connection": { "achievementId": "6aa29a5f88194da38c75b628", "badgeRewardId": "6aa29a3588194da38c75b51a", "eventName": "planet.relay.restored", "points": 10, "xp": 10, "enabled": false } } } } } ``` ### Keep the earning and spending contracts explicit Each business has two learning challenges with one required multiple-choice action each, six puzzle achievements with matching collectible badges, three shop rewards, a disabled daily streak and a selected draft weekly XP leaderboard. The daily question is repeatable; it is not an automatically rotating question bank. Community creation also seeds two unused draft leaderboards. | Completion | Points | XP | Frequency | | - | -: | -: | - | | Welcome challenge | 20 | 20 | Once | | Each of six physical puzzles | 10 | 10 | Once per puzzle | | Daily learning challenge | 10 | 10 | Once per organisation day | | Complete first visit | 90 | 90 | Welcome, six puzzles and daily learning | These are configured payouts, not new live payout results. The required learning actions have zero action XP, with points and bonus XP on the challenge's distribution. The collectible badges add no separate points or XP. Spending points leaves XP intact; the streak has no points/XP milestone attached. Each wallet offers a 30-point demo perk, a 60-point demo perk and a 60-point real download. Ninety points buys the basic demo plus either 60-point choice. Both demo cards, their artwork and their native wallet content explicitly say "Demo perk. No real redemption." They contain no working voucher codes or goods/services entitlement. | Business | 30-point demo | 60-point demo | 60-point download | | - | - | - | - | | Morrow Roast | Tasting flight pass | Coffee on us | The home-brew notebook | | Still Skin | First look pass | Skincare discovery set | The bathroom shelf edit | | Pace & Form | Guest class pass | Training workshop pass | The consistency kit | | Goodkind Pets | Treat tasting pass | Enrichment box voucher | The good-human notebook | | Wildward Supply | New gear preview | Trail essentials voucher | The weekend fieldbook | | Chapter & Kin | Book-club guest pass | Next read voucher | The book-club box | | Fold Studio | New drop preview | Wardrobe credit voucher | The wear-it-again planner | | Market Lane | Member offers pass | Weekly shop voucher | The pantry planner | The eight PDFs contain 43 pages of finished examples and printable worksheets, with matching HTML. Their preview responses returned `200`, `application/pdf` and bytes matching the local SHA-256 hashes. Stable delivery remains pending. A native wallet entitlement does not make a public static PDF private; choose public distribution or implement a verified entitlement check before delivery. Creating a shop reward does not allocate it to the shop. Use `reward_assets.add_to_shop`, then read Admin `GET /rewards/{rewardId}` and verify the actual `allocations[]` ID, `isShop: true`, price, supply and amount. For owned rewards, paginate Integration `GET /me/rewards?communityId=...&limit=100&skip=...` and match member, organisation, community, reward and allocation. Do not infer wallet ownership from the global earned-reward count. ### Reuse exact events and prove the current day The brand cues change; the six validated physical inputs and event names stay the same. A branded pipe puzzle still uses pipe turns and launch controls. | Physical input | Exact custom event | | - | - | | Relay cables | `planet.relay.restored` | | Pipe routing and launch | `planet.draft.approved` | | Three-note echo | `planet.trail.completed` | | Tower balance | `planet.tower.opened` | | Rungs and held climb | `planet.cloud.climbed` | | Sphinx symbol alignment | `planet.pyramid.opened` | Create the achievement through Admin `POST /achievements` with an explicit community-scoped query and matching count criterion. The existing [NJ-2534 mapper defect](https://linear.app/we-nudj/issue/NJ-2534/preserve-exact-custom-event-names-and-source-filters-in-mcp) can replace a supplied custom name with `custom_custom`; verify the raw `GET /achievements/{id}` result. This is the query/criterion excerpt for the relay achievement, with the other required create fields omitted: ```json theme={null} { "queries": [{ "type": "event", "alias": "well_connected", "parameters": { "eventName": "planet.relay.restored", "eventCategory": "custom", "eventSubCategory": "custom" }, "isCommunityScoped": true }], "tiers": [{ "tierNumber": 1, "criteria": { "type": "count", "parameters": { "query": "well_connected", "operator": ">=", "value": 1 } } }] } ``` Attach 10 points, 10 XP and one matching badge with the native reward distribution API. Submit gameplay through the same [signed event proof](#sign-and-submit-the-native-event-proof) and stable nonce contract used earlier in this guide. A successful content write proves the configuration; a native claim and balance read prove the payout. The new streak trigger configuration is deliberately separate from the widget's native check-in button: ```json theme={null} { "platform": [], "custom": ["planet.check-in"] } ``` Before issuing a signed `planet.check-in` proof, the server must verify the selected persona's native daily-challenge completion for the current `Europe/Berlin` day. Reuse one nonce for that member, business and day across retries. Integration `GET /challenges/{id}` exposes `userState.completionsInPeriod` and attempt history, but no `completedAt` or `lastCompletedAt` field. Do not invent a completion timestamp from rounded `completionTime`. Midnight, stale-cache and duplicate-proof acceptance tests remain required; the existing streak timeline timezone defect is tracked separately as NJ-2543. ### Read back seeded rules, rendered sections and artwork Creating a draft community seeded a live referral backing rule paying ten points to each side. All eight new referrer/referee distributions were set to zero and read back. Its hidden lifecycle remains live because the supported referral API has no lifecycle write. Hiding a panel is not disabling a rule. [NJ-2583](https://linear.app/we-nudj/issue/NJ-2583/allow-referral-lifecycle-control-when-a-draft-community-seeds-live) tracks this gap; the original Planet referral campaign was untouched. Use implemented panel sections `earn`, `spend`, `achievements`, `streak` and `leaderboard`. The accepted IDs `badgeShowcase` and `featuredRewards` currently map to null renderers, so these drafts leave them disabled. `pointsForSpend` means earning from purchases, not the shop. [NJ-2585](https://linear.app/we-nudj/issue/NJ-2585/do-not-silently-accept-widget-sections-that-have-no-renderer) tracks the misleading successful configuration response. All 48 business-specific badges are complete. Their palettes and shop cues are visible at 96 pixels, and each retains its puzzle's object. The final transparent URLs are attached to all 48 achievements and 48 collectible rewards. Raw read-backs preserve event queries, criteria, distributions and draft status. Six coffee-coloured puzzle badge cutouts with bean details, shown at 96 pixels on cream and dark ink Six pink Market Lane puzzle badges with basket details, shown at 96 pixels on cream and dark ink Generation initially baked checkerboards into RGB. Nudj `image.ingest` made the sources durable, then `image.edit` with `edit_type: "background_removal"` produced RGBA cutouts. Check interior holes as well as corners. The coffee ring retained checkerboard pixels after removal, and a second pass worsened it. Both outputs were rejected. A targeted edit made its centre a solid cream medallion with a bean cue, followed by verified outer removal. [NJ-2586](https://linear.app/we-nudj/issue/NJ-2586/detect-residual-checkerboards-in-background-removed-badge-cutouts) records the quality failure. Tool success alone does not establish usable alpha. ### Keep release checks separate from content completion | Hold | Required evidence before enabling | | - | - | | Welcome payout, [NJ-2532](https://linear.app/we-nudj/issue/NJ-2532/award-challenge-points-from-effective-action-xp-and-completed-zero-xp), [PR #4436](https://github.com/Nudj-digital/monorepo/pull/4436) | The repair merged on 10 September 2026. API deployment and a fresh member receiving exactly 20 points and 20 XP through the real welcome route remain unverified. Optional earnings or changed action XP do not prove this. | | Widget identity, [NJ-2582](https://linear.app/we-nudj/issue/NJ-2582/bind-embedded-widget-sessions-to-the-requested-identity-and), [PR #4453](https://github.com/Nudj-digital/monorepo/pull/4453) | The repair was draft, unmerged and undeployed when this chapter was prepared. Its 87 focused tests, typecheck and source review passed; real concurrent-tab browser proof is still blocked by the [NJ-2527 seed-runner failure](https://linear.app/we-nudj/issue/NJ-2527/admin-e2e-seed-runner-fails-with-err-require-esm-after-nudj). Test actual wallet and write identity through switch, reload, broadcasts, 401 refresh and pending requests. | | Guest/account and durable progress, [NJ-2584](https://linear.app/we-nudj/issue/NJ-2584/save-planet-nudj-progress-with-one-player-and-admin-login) | Verified signup/linking, recovery and duplicate-safe progress migration on durable production storage. Content setup does not prove these paths. | | Download delivery | Replace temporary preview links only after stable PDF delivery is verified. Keep entitlement/privacy claims consistent with how the files are served. | The draft identity repair adds `identityBinding: {memberId, requestId}` and an SDK `identity_ready` event carrying the resolved `{memberId, communityId, requestId}`. The DOM event is `nudj:identity_ready`; failure uses `identity_failed` and `nudj:identity_failed`. Match the expected values after the SDK validates origin, frame and generation. Current frame-ready messages and accepted input JWTs do not prove which member the iframe resolved. Recheck the deployed contract before using these new events; draft source and passing unit tests are not release proof. The website's `scripts/check-planet-persona-content.cjs` validates the manifest and local artefacts. `scripts/check-planet-persona-native.cjs` audits live Admin read-backs without awarding points or publishing content. Keep their results separate from the remaining native payout, wallet debit/reload, duplicate, A-to-B-to-A, parallel-tab, Berlin-day and original-progress acceptance tests. ## Award tiered time and walking badges You can use native achievement tiers for increasing gameplay milestones. Planet Nudj has two four-tier achievements, each awarding one collectible badge per tier with zero points and zero XP: | Achievement | Milestones | Badge progression | | - | - | - | | Just one more minute | 1, 3, 5, 10 active minutes | Just having a look; One more minute; Research, apparently; I live here now | | Sole survivor | 100, 300, 600, 1,200 in-game metres | Just stretching my pixels; Scenic route specialist; Sole trader; Planetary wear and tear | Paper-cut clock badges progress from looking over a map to settling into an armchair Paper-cut walking badges show a boot, a scenic path, a worn sole and a boot standing on a tiny planet In-game metres are world units, not physical exercise. Count server-accepted manual walking only; guided travel, map autorun, flight and teleports do not qualify. Active time needs a server elapsed-time cap plus visible-page and idle checks. A browser-supplied duration is not proof of play. ### Register and validate the event schemas Create an organisation schema with Admin `POST /event-schemas`: ```json theme={null} { "eventName": "planet.play.milestone", "description": "One server-verified event per active play threshold per member", "properties": [ {"name":"seconds","type":"number","required":true,"integer":true,"min":60,"max":600} ] } ``` Publish it with `PATCH /event-schemas/{id}` and `{"state":"published"}`. Create the distance schema the same way with event name `planet.distance.milestone`, property `distance`, minimum 100 and maximum 1200. These routes worked against Derek Demos; no UI fallback was needed. You can validate a payload without recording an event or awarding anything: ```http theme={null} POST /api/v2/admin/event-schemas/validate Content-Type: application/json ``` ```json theme={null} { "eventName": "planet.play.milestone", "payload": {"seconds":60,"nonce":"documentation-dry-run","schemaVersion":1} } ``` ```json theme={null} {"valid":true,"schemaExists":true,"issues":[]} ``` All eight milestone values passed the deployed dry-run validator. Ten invalid cases covering missing values, strings, fractions and out-of-range numbers were rejected. Numeric bounds do not enforce the exact threshold list: the game server must allow only 60/180/300/600 seconds or 100/300/600/1200 metres. ### Count reached milestones once Emit one signed custom event at each legitimately reached threshold, using the existing [event proof](#sign-and-submit-the-native-event-proof) flow. Retain a stable nonce per member, community, metric and threshold on retries. When a sample crosses multiple thresholds, emit every missing reached milestone in order. Do not add cumulative values together. Create one community-scoped event query for each metric. For time: ```json theme={null} { "type": "event", "alias": "play_milestone", "parameters": { "eventName": "planet.play.milestone", "eventCategory": "custom", "eventSubCategory": "custom" }, "isCommunityScoped": true } ``` Use four native count criteria with target values 1, 2, 3 and 4. For example, tier 3 uses: ```json theme={null} { "tierNumber": 3, "details": {"name":"Research, apparently","description":"Five minutes of very important button research."}, "criteria": {"type":"count","parameters":{"query":"play_milestone","operator":">=","value":3}}, "rewardDistribution": null } ``` ### Attach a badge to each tier The raw Admin endpoint supports `tierNumber`; the MCP `reward_allocations` tool currently does not expose it. The gap is tracked in [NJ-2598](https://linear.app/we-nudj/issue/NJ-2598/expose-achievement-tiernumber-in-mcp-reward-allocations). Omitting this field targets tier 1. ```http theme={null} POST /api/v2/admin/achievements/{achievementId}/distribution ``` ```json theme={null} { "tierNumber": 3, "pointsToDistribute": 0, "bonusXpToDistribute": 0, "rewardsConfig": { "mechanism": "all", "amountToDistribute": 1, "allocations": [{"rewardId":"","allocationType":"assets","amountToDistribute":1}] } } ``` Use a free, unlimited, auto-redeemed badge reward and omit allocation `supply` for shared unlimited badges. Read all four achievement tiers back after assignment: four requests without `tierNumber` can repeatedly change only the first tier. For member claims, read the actual active tier and pass its number to Integration `POST /achievements/{achievementId}/claim`. The Integration source schema exposes `tiers[].userState` and `activeTier.userState`; verify those fields against the deployed response rather than assuming a single-tier shape. If all four tiers complete before any claim, `highestCompletedTier` is already 4 while `activeTier` correctly starts at 1. Claim and refresh the active tier until the remaining completed tiers are collected. Current verification covers two original-community live achievements, eight live badge rewards, all four separate distributions and images on each, zero points/XP, exact custom event queries and the published schemas. All eight artwork URLs returned HTTP 200. An isolated test driver then accumulated 600 active seconds and 1,440.329 accepted in-game metres over ten real minutes, using 1,200 game API requests with no shortened clock or prefilled counters. Independent production reads confirmed all eight milestone events exactly once, each with its correct published schema and valid payload. This proves real-clock game-server delivery; it is not a human browser play-through. The same member then claimed all eight badges through the actual game cards, with time and walking tiers each advancing 1 through 4. Each duplicate attempt returned game HTTP 409; native points and XP stayed at zero before and after every claim. This verifies the game claim action, not a repair of the separate native-widget claim defect below. A configuration read-back or schema dry run alone is not a gameplay test. Planetary wear and tear badge with four completed checkpoints and a tier-four claim button The first time-tier claim succeeded before a test-harness response-reading error. The resumed run verified its native advance and rejected replay before claiming the remaining seven tiers. The evidence retains that missing earlier progress payload as null; it does not reconstruct an unrecorded result. ### Native widget claim limitation The current widget source omits `tierNumber` in its achievement hook, claim modal and home-section mutations. The API defaults that omission to tier 1. Its home section also hides claimed achievements by ID alone, which can hide later claimable tiers. This source-traced defect is tracked in [NJ-2599](https://linear.app/we-nudj/issue/NJ-2599/pass-the-active-achievement-tier-when-claiming-from-the-native-widget). Until the native fix ships, the game must claim the actual eligible tier through its server Integration API action; the official widget can still show the real native badges. Do not describe this workaround as a fixed native widget claim flow. # MCP Admin Setup Source: https://docs.nudj.cx/developer/mcp-admin-setup Generate MCP tokens from the admin panel and connect Claude Desktop to Nudj. Model Context Protocol (MCP) lets AI assistants talk securely to your Nudj organisation. This page covers the **admin-panel flow**: mint an MCP token, then wire up Claude Desktop (or another stdio-based client) to use it. For Claude Code (the CLI) see [Claude Code Setup](/developer/mcp/claude-code). For the full tool catalog see [MCP Server](/mcp-server/introduction). ## Step 1: Generate an MCP token 1. Open the admin panel at `https://.nudj.cx/admin`. 2. Navigate to **Settings → Developer → MCP Tokens**. 3. Click **Generate Token**. 4. **Name the token** (e.g. "My Claude Desktop"). 5. **Pick an RBAC scope** — the token inherits the role's permissions: * **Viewer**: read-only across the MCP tool surface. * **Moderator / Manager**: read + targeted writes. * **Admin / SuperAdmin**: full read-write on Nudj resources. 6. **Copy the token immediately** — it's displayed once. MCP tokens grant your RBAC-scoped privileges to whichever AI agent you hand them to. Pick the lowest scope that still lets the agent do useful work. ## Step 2: Configure Claude Desktop Claude Desktop uses a stdio transport — it spawns `mcp-remote` as a child process. Edit the Claude Desktop config file: * **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` * **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` Add the `nudj` server: ```json theme={null} { "mcpServers": { "nudj": { "command": "npx", "args": [ "-y", "mcp-remote", "https://mcp.nudj.cx/mcp", "--header", "Authorization: Bearer YOUR_TOKEN_HERE", "--header", "x-api-domain: https://YOUR_SUBDOMAIN.nudj.cx/api/v2/admin" ] } } } ``` Replace `YOUR_TOKEN_HERE` and `YOUR_SUBDOMAIN` with the values from step 1. Restart Claude Desktop. The Nudj tools should appear in the MCP panel. ## Step 3: Start chatting Ask Claude natural-language questions and it will invoke the MCP tools to answer: > "Which of my challenges completed the most times this week?" > > "Create three quiz challenges about space travel, each worth 50 points, with a 'Science Fans' tag." > > "Summarise points distributed in the last 30 days and flag any unusual spikes." ## Security & safety The token can only do what your role allows. A Viewer token fails `403` on mutations. Delete the token from **Settings → Developer → MCP Tokens**. The AI loses access within seconds. Tokens are locked to a single organisation — they won't work against other orgs, even on accident. Your data is used to answer your prompts in that session. Anthropic does not use MCP traffic to train models. ## Common questions The AI immediately loses access to your Nudj community. You'll need to generate a new token and update the Claude Desktop config to reconnect. Yes — generate one token per assistant or teammate so you can track and revoke them independently. No. Any client that speaks MCP works. For the Claude Code CLI, see [Claude Code Setup](/developer/mcp/claude-code). For a custom HTTP client, see [Bearer + Domain Headers](/developer/mcp/bearer-and-domain). ## Related The full tool catalog and transport details. Developer-side `.mcp.json` for the Claude Code CLI. # Bearer + x-api-domain Headers Source: https://docs.nudj.cx/developer/mcp/bearer-and-domain The canonical auth pattern for any HTTP-based MCP client connecting to the Nudj MCP server. Every HTTP-based MCP client — Claude Code, Cursor, ChatGPT, custom `mcp-remote` wrappers — authenticates to the Nudj MCP server with **two headers**: ``` Authorization: Bearer x-api-domain: https://.nudj.cx/api/v2/admin ``` The MCP server validates the token against Nudj's auth layer, then uses `x-api-domain` to route tool calls to the correct organisation's Admin API. **Both headers are required.** stdio-based MCP clients (Claude Desktop, older Cursor) use env vars instead of headers. This page is for HTTP transports. ## Why two headers? The MCP server is a **single deployment** serving many organisations. The Bearer token identifies the user; the `x-api-domain` header tells the MCP server which org the user is operating on during this session. Nudj enforces that the token's claimed org matches the subdomain in `x-api-domain` — see [Bearer Token Authentication](/developer/bearer-token-auth#organisation-scoping-why-this-matters) for why. ## Generate a token 1. Sign into the admin panel at `https://.nudj.cx/admin`. 2. Navigate to **Settings → Developer → MCP Tokens**. 3. Click **Generate Token** and pick an RBAC scope (Viewer, Moderator, Manager, Admin, or SuperAdmin). 4. Copy the token immediately — it is shown once. MCP tokens inherit your RBAC role. A Viewer-scoped token will fail `403` on any mutation tool; match the scope to the agent's actual needs. ## Configure an HTTP MCP client Most clients accept an `mcp-remote` config block. The canonical shape: ```json theme={null} { "mcpServers": { "nudj": { "command": "npx", "args": [ "-y", "mcp-remote", "https://mcp.nudj.cx/mcp", "--header", "Authorization: Bearer ${NUDJ_MCP_TOKEN}", "--header", "x-api-domain: https://.nudj.cx/api/v2/admin" ] } } } ``` Replace `` with your organisation's subdomain. Store the token in an env var rather than pasting it into the config directly. ## Endpoint map | Environment | MCP URL | `x-api-domain` | | - | - | - | | Production | `https://mcp.nudj.cx/mcp` | `https://.nudj.cx/api/v2/admin` | | Development | `https://mcp-dev.nudj.cx/mcp` | `https://devapi.nudj.cx/api/v2/admin` | | Local | `http://localhost:4010/mcp` | `https://localhost:3000/api/v2/admin` | For local development, see [Claude Code Setup](/developer/mcp/claude-code) — the local flow needs a TLS bypass. ## Verifying the connection Once configured, the client exposes Nudj tools in its tool catalog. Quick sanity check — ask the agent: > "Use the Nudj MCP to list my five most recent challenges." If auth is wrong, you'll see a `401` or `403` in the client's error log. Common causes: * Token copied with leading/trailing whitespace * `x-api-domain` missing the `/api/v2/admin` suffix * Token scoped to a different org than `x-api-domain` * Token expired (re-generate from the admin panel) ## Related Full `.mcp.json` walkthrough for Claude Code specifically. The full tool catalog exposed by the Nudj MCP server. # Claude Code Setup Source: https://docs.nudj.cx/developer/mcp/claude-code Configure Claude Code to talk to the Nudj MCP server via .mcp.json. Claude Code reads MCP server configuration from a `.mcp.json` file at your project root. This page walks through wiring Nudj into Claude Code for both cloud-hosted and local MCP servers. This is the developer workflow. For admin-panel / Claude Desktop setup, see [MCP Admin Setup](/developer/mcp-admin-setup). ## Prerequisites * Claude Code installed and running in a project directory. * An MCP token from **Settings → Developer → MCP Tokens** (see [Bearer + x-api-domain](/developer/mcp/bearer-and-domain#generate-a-token)). * `npx` on `PATH` (comes with Node.js). ## Cloud MCP (recommended) Add Nudj's hosted MCP server to your project's `.mcp.json`: ```json theme={null} { "mcpServers": { "nudj-dev": { "command": "npx", "args": [ "-y", "mcp-remote", "https://mcp-dev.nudj.cx/mcp", "--header", "Authorization: Bearer ${NUDJ_DEV_MCP_TOKEN}", "--header", "x-api-domain: https://devapi.nudj.cx/api/v2/admin" ] } } } ``` Then export the token in your shell so Claude Code can inject it at runtime: ```bash theme={null} # ~/.zshrc or ~/.bashrc export NUDJ_DEV_MCP_TOKEN='paste-your-token-here' ``` Reload your shell and restart Claude Code. The Nudj tools should appear in the MCP tool list. ## Local MCP (contributors) If you're iterating on the MCP server itself, point Claude Code at your local instance: ```json theme={null} { "mcpServers": { "nudj-local": { "command": "npx", "args": [ "-y", "mcp-remote", "http://localhost:4010/mcp", "--header", "Authorization: Bearer ${NUDJ_LOCAL_MCP_TOKEN}", "--header", "x-api-domain: https://localhost:3000/api/v2/admin" ] } } } ``` Then start the MCP server with TLS verification disabled (the local Nudj API uses self-signed HTTPS): ```bash theme={null} cd apps/mcp-server NODE_TLS_REJECT_UNAUTHORIZED=0 bun run dev ``` **Important local-dev notes:** * MCP URL is `http://localhost:4010/mcp` (HTTP, not HTTPS, and note the `/mcp` path). * `x-api-domain` uses `https://localhost:3000/api/v2/admin` — HTTPS with the full `/api/v2/admin` suffix. * All three apps (admin, API, MCP) must share the same `NEXTAUTH_SECRET` in their `.env.local` files, otherwise token validation will fail with `401`. ## Reconnecting after a config change Claude Code caches MCP connections. After editing `.mcp.json` or rotating a token: ```bash theme={null} pkill -f mcp-remote # kill the stale mcp-remote process ``` Then run `/mcp` in Claude Code to reconnect. ## Combining with other MCP servers Claude Code happily runs multiple MCP servers side by side. Common combos alongside `nudj`: ```json theme={null} { "mcpServers": { "nudj-dev": { "...": "..." }, "jira": { "...": "..." }, "vercel": { "...": "..." } } } ``` Each server exposes its own tools; Claude Code aggregates them. There is no cross-server interference — they run as independent subprocesses. ## Troubleshooting * Confirm `.mcp.json` is at the project root (not inside a subdirectory). * Check Claude Code's MCP log for startup errors. * Verify the env var holding your token is exported in the shell Claude Code inherits. * Token expired or has leading/trailing whitespace. Regenerate it. * For local dev: check `NEXTAUTH_SECRET` matches across admin, API, and MCP `.env.local` files. * Token is valid but its claimed organisation doesn't match `x-api-domain`. Re-issue a token from the correct org's admin panel. * Token's RBAC scope is too low for the tool you're calling. Regenerate at a higher scope. * Ensure you started the MCP server with `NODE_TLS_REJECT_UNAUTHORIZED=0`. * Verify the API is running on `https://localhost:3000` (not 3100 — that's a worktree offset). ## Related The auth pattern shared by all HTTP MCP clients. Every tool the Nudj MCP server exposes. # OAuth Integration Setup Source: https://docs.nudj.cx/developer/oauth-authentication Configure OAuth 2.0 / OpenID Connect to enable login via your authentication provider This guide explains how to configure your OAuth 2.0 / OpenID Connect (OIDC) provider so users can log into Nudj using your existing authentication system. **Use this method if**: Users arrive at Nudj directly (not already authenticated) and initiate a login from the Nudj login page. **If users are already authenticated elsewhere**, use [API Link User Token Authentication](/developer/api-link-user-authentication) instead. ## Quick Start 1. **Add OIDC configuration in Nudj**: Go to Organisation Settings → Authentication and enter your provider details 2. **Whitelist Nudj's redirect URI**: Add the callback URL to your OAuth provider's configuration 3. **Test the flow**: Log in via your provider and verify the integration works ## Determine the Authentication Method Use OAuth 2.0 / OIDC if: * Users will arrive at Nudj not authenticated * You want to follow industry-standard authentication flows * Users may access multiple services and need centralized authentication If users are already authenticated in your system, use [API Link User Token Authentication](/developer/api-link-user-authentication) instead. ## Add Your OIDC Configuration in Nudj Navigate to your Nudj admin panel at `https://your-domain/admin`: 1. Go to **Organisation Settings → Authentication** 2. Create a new OIDC configuration with these details: Your OAuth provider's Client ID Your OAuth provider's Client Secret The OIDC base URL (discovery endpoint) from your provider. Used for token verification. ### Required Scopes Configure these scopes in your provider: * **openid** (required) * **profile** (required) * **email** (required) * **offline\_access** (optional, for refresh tokens) Once you save the configuration, **Nudj generates a unique `oidcConfig.id`** for your environment. You'll need this for the next step. ## Whitelist Nudj's Redirect URI After creating the OIDC configuration in Nudj, you'll have a callback URL. Add this exact URL to your OAuth provider's redirect URI whitelist (no trailing slash): ``` https://${rootDomain}/api/auth/callback/organisation-oidc-${oidcConfig.id} ``` Where: * **`${rootDomain}`** = Your Nudj domain (found in Organisation Settings → Domains) * **`${oidcConfig.id}`** = The ID generated when you created the OIDC config ### Example If your domain is `loyalty.mycompany.com` and your `oidcConfig.id` is `abc123def456`, the redirect URI would be: ``` https://loyalty.mycompany.com/api/auth/callback/organisation-oidc-abc123def456 ``` Add this exact URL to your OAuth provider's configuration and save. ## Testing the Integration 1. Confirm your OAuth provider lists the Nudj redirect URI correctly 2. Verify all required scopes are enabled in your provider 3. Log out of Nudj (if currently logged in) 4. On the Nudj login page, click **"Log in with \[Your Provider Name]"** 5. Complete authentication with your provider 6. Verify you're redirected back to Nudj and fully authenticated ## Troubleshooting Make sure the OIDC configuration is saved in Nudj and enabled. It may take a few minutes to appear on the login page after enabling. The redirect URI in your OAuth provider must match **exactly** what Nudj generated. Check for: * Trailing slashes (Nudj doesn't use them) * Correct domain * Correct `oidcConfig.id` Any mismatch will cause the OAuth flow to fail. Ensure your OAuth provider has the required scopes (openid, profile, email) enabled. Check your provider's documentation if you're unsure how to enable additional scopes. Verify that the `issuer` URL you provided in Nudj matches your OAuth provider's OIDC discovery endpoint. This is used to validate tokens. If the issuer is wrong, token verification will fail. ## Common OAuth Providers | Provider | Issuer URL Format | Setup Docs | | - | - | - | | Auth0 | `https://your-domain.auth0.com` | [https://auth0.com/docs/get-started](https://auth0.com/docs/get-started) | | Okta | `https://your-domain.okta.com` | [https://developer.okta.com/docs/guides/implement-oauth-for-okta/](https://developer.okta.com/docs/guides/implement-oauth-for-okta/) | | Azure AD | `https://login.microsoftonline.com/{tenant}/v2.0` | [https://docs.microsoft.com/en-us/azure/active-directory/](https://docs.microsoft.com/en-us/azure/active-directory/) | | Keycloak | `https://your-keycloak-domain/realms/your-realm` | [https://www.keycloak.org/docs](https://www.keycloak.org/docs) | | Custom OIDC | Check your provider's documentation | See your provider's OIDC configuration | ## Need Help? Contact Nudj support if you encounter any issues during setup or testing. # Action Webhooks Source: https://docs.nudj.cx/developer/webhooks/action-webhooks React to individual action completions, not just challenge-level events. Most challenge events fire once per challenge completion. **Action webhooks** fire once per individual action completed inside a challenge — giving you a finer-grained signal when a user answers a quiz question, watches a video, clicks a link, or completes any of Nudj's 28+ action types. Action webhooks are useful for real-time personalisation and analytics. If you only need end-of-challenge events, stick with `challenge-completion`. ## When to use action webhooks * **Progressive personalisation**: update a recommendation engine as each action is completed rather than waiting for the full challenge. * **Granular analytics**: feed a data warehouse with per-step engagement metrics. * **Custom side-effects**: trigger downstream workflows (e.g. email a partner) when a specific action type is completed, regardless of which challenge it belongs to. ## The `action-participation` event Subscribe to the `action-participation` event: ```bash theme={null} curl -X PUT "https://{subdomain}.nudj.cx/api/v2/admin/webhooks/configs" \ -H "x-api-token: YOUR_ADMIN_API_TOKEN" \ -H "Content-Type: application/json" \ -d '[ { "url": "https://your-app.com/webhooks/nudj", "httpMethod": "POST", "isEnabled": true, "maxRetries": 3, "events": ["action-participation"] } ]' ``` ### Example payload ```json theme={null} { "id": "652f8a3c1e4f7d0012a3b4c5", "eventCategory": "action", "eventSubCategory": "action-participation", "organisationId": "5f7d1e2a...", "communityId": "5f7d1e2a...", "userId": "60f7b8c8e7d4a2b1c9e3f4a3", "eventSourceId": "60f7b8c8e7d4a2b1c9e3f4a4", "payload": { "actionId": "60f7b8c8e7d4a2b1c9e3f4a4", "actionType": "QUIZ_MULTIPLE_CHOICE", "challengeId": "60f7b8c8e7d4a2b1c9e3f4a0", "isCorrect": true, "completedAt": "2026-03-14T10:21:45.102Z", "pointsEarned": 10, "xpEarned": 5 }, "createdAt": "2026-03-14T10:21:45.102Z" } ``` * `eventSourceId` is the **action participation** ID, not the action definition. If you need the action definition, use `payload.actionId`. * `payload.actionType` matches the enum in [action definitions](/api-reference/admin/get-actions). ## Filtering by action or challenge Combine `action-participation` with [entity-level filtering](/developer/webhooks/event-filtering) to subscribe to only a specific action or challenge: ```json theme={null} { "events": ["action-participation"], "entityFilters": { "action-participation": ["60f7b8c8e7d4a2b1c9e3f4a4"] } } ``` ## Volume expectations Action webhooks fire **far more frequently** than challenge webhooks. A 5-action challenge fires 5 `action-participation` events plus 1 `challenge-completion` event per user completion. Plan accordingly: * Size your handler for peak burst throughput, not average. * Process asynchronously — acknowledge the delivery with `200 OK` fast, then enqueue work. * Use entity filters to cut volume before it reaches your handler. ## Action completion vs challenge completion | Event | Fires when | | - | - | | `action-participation` | A user finishes one action inside a challenge | | `challenge-completion` | The user finishes all required actions in a challenge | For typical loyalty flows, you only need `challenge-completion`. Reach for `action-participation` when you need real-time signals at the step level. ## Related Full event list, including all action-related events. Scope subscriptions to specific actions or challenges. # Event Catalog Source: https://docs.nudj.cx/developer/webhooks/event-catalog Detailed catalog of webhook events with trigger conditions and use cases ## Event Catalog Overview This catalog provides detailed information about each webhook event, including exact trigger conditions, related events, and practical use cases. ## Challenge Events ### challenge-started **When it fires**: The moment a user clicks "Start Challenge" and the system creates a challenge participation record. **Frequency**: Once per user per challenge attempt **Idempotency**: Starting the same challenge again creates a new event ✅ Fires when: * User clicks "Start Challenge" button * API call to `/challenges/{id}/start` succeeds * Challenge participation record is created ❌ Does NOT fire when: * User views challenge details * Challenge is already completed * User lacks permission to start challenge Events that commonly fire in sequence: 1. `challenge-started` (this event) 2. `action-participation` (for each action completed) 3. `challenge-completion` (when all actions done) 4. `reward-distribution` (if rewards configured) 5. `achievement-completion` (if triggers achievement) * Send "Good luck!" email * Track engagement metrics * Start completion timer * Notify team members * Update user journey stage ### challenge-completion **When it fires**: When all required actions in a challenge are marked as complete. **Frequency**: Once per successful challenge completion **Idempotency**: Completing an already-completed challenge does not fire again ✅ Fires when: * Last action in challenge is completed * All required actions have passing scores * Challenge completion record is created ❌ Does NOT fire when: * Partial challenge completion * Challenge expired before completion * User fails required actions Typically followed by: * `reward-distribution` (immediate) * `achievement-completion` (if applicable) * `streak-extended` (if maintains streak) * `xp-gained` (if XP awarded) * Send congratulations message * Distribute rewards * Update completion stats * Check achievement progress * Trigger follow-up campaigns ## Reward Events ### reward-distribution **When it fires**: When the system grants rewards to a user (points, XP, items). **Frequency**: Can fire multiple times per trigger event **Note**: This is the most data-rich webhook event ✅ Fires when: * Challenge completion triggers reward rules * Achievement unlocked with rewards * Manual reward distribution by admin * Scheduled reward distribution (e.g., leaderboard) ❌ Does NOT fire when: * User purchases from shop * Reward claimed but not distributed * Distribution rules not met Contains comprehensive information: * User details (ID, email, externalUserId) * Points earned * XP earned * Reward items with metadata * Trigger event information * Campaign associations * Issue gift cards * Send reward notifications * Update loyalty points * Fulfill physical rewards * Sync with CRM ### reward-claim **When it fires**: When a user claims an available reward from their rewards list. **Frequency**: Once per reward claim action **Different from**: `reward-distribution` (automatic) vs `reward-claim` (user action) ✅ Fires when: * User clicks "Claim Reward" * Reward is available and not expired * User has sufficient permissions ❌ Does NOT fire when: * Reward auto-distributed * Reward expired * Already claimed * Send claim confirmation * Start fulfillment process * Track claim rates * Set expiration reminders ### reward-redemption **When it fires**: When a user redeems/uses a previously claimed reward. **Frequency**: Once per redemption **Lifecycle**: Distribution → Claim → Redemption ✅ Fires when: * User clicks "Redeem" on claimed reward * Redemption code is used * Admin marks as redeemed ❌ Does NOT fire when: * Reward claimed but not redeemed * Redemption fails validation * Reward expired * Activate voucher codes * Track redemption rates * Process payments * Send redemption receipt ## Commerce Events ### shop-purchase **When it fires**: When a user spends points to purchase items from the shop. **Frequency**: Once per purchase transaction **Contains**: Purchase details, points spent, items received ✅ Fires when: * Purchase transaction completes * User has sufficient points * Item is in stock ❌ Does NOT fire when: * Insufficient points * Item out of stock * Purchase cancelled * Trigger fulfillment * Send order confirmation * Update inventory * Deduct points * Track purchase analytics ## Achievement Events ### achievement-completion **When it fires**: When a user meets all criteria for an achievement. **Frequency**: Once per achievement per user **Types**: Can be one-time or recurring (daily, weekly, monthly) ✅ Fires when: * Achievement criteria met * Progress reaches 100% * Recurring achievement resets and completes ❌ Does NOT fire when: * Partial progress made * Already completed (non-recurring) * Achievement disabled * Issue badges * Send achievement notification * Unlock exclusive rewards * Update user profile * Trigger next achievement tier ## Social Events ### post-like **When it fires**: When a user likes a community post. **Frequency**: Once per like action **Reversible**: Unliking does not fire an "unlike" event ✅ Fires when: * User clicks like button * Like record created * Post is active ❌ Does NOT fire when: * Already liked * Post archived * User lacks permission * Track engagement * Notify post author * Update engagement achievements * Calculate trending posts ### post-comment **When it fires**: When a user comments on a post. **Frequency**: Once per comment **Contains**: Post ID and comment ID ✅ Fires when: * Comment submitted * Comment passes moderation * Comment record created ❌ Does NOT fire when: * Comment fails moderation * Comments disabled * User banned from commenting * Notify participants * Track discussions * Trigger moderation * Email digest updates ## Gamification Events ### streak-extended **When it fires**: When a user maintains their activity streak. **Frequency**: Once per day when streak conditions met **Reset**: Streak resets if user misses a day ✅ Fires when: * Daily activity requirement met * Streak counter increments * Within streak time window ❌ Does NOT fire when: * Activity already recorded today * Streak broken (missed day) * Streak feature disabled * Update streak counter * Award streak achievements * Send streak reminders * Unlock streak rewards * Track retention metrics ## User Events ### auth.email **When it fires**: When a new user account is created via email authentication. **Frequency**: Once per user creation **Contains**: Basic user information ✅ Fires when: * Email signup completed * User record created * Email verified (if required) ❌ Does NOT fire when: * Social login used * User already exists * Signup fails validation * Send welcome email * Create CRM contact * Award signup bonus * Track acquisition * Notify sales team ### organisation.invite-admin-user **When it fires**: When an admin user is invited to an organization. **Frequency**: Once per invitation **Scope**: Organization-level administrative event ✅ Fires when: * Admin invitation sent * Invitation record created * Email dispatched ❌ Does NOT fire when: * Regular user invited * Invitation fails * User already admin * Send invitation email * Audit admin access * Log security events * Track organization growth ## Event Chains Common event sequences you'll encounter: ### Challenge → Reward Flow ``` 1. challenge-started 2. challenge-completion 3. reward-distribution 4. achievement-completion (if applicable) 5. streak-extended (if maintains streak) ``` ### Shop Purchase Flow ``` 1. shop-purchase 2. reward-distribution (item granted) 3. achievement-completion (shopping achievement) ``` ### Social Engagement Flow ``` 1. post-like or post-comment 2. achievement-completion (engagement achievement) 3. reward-distribution (if rewards configured) ``` # Entity-Level Filtering Source: https://docs.nudj.cx/developer/webhooks/event-filtering Subscribe to webhook events for specific challenges, achievements, or rewards instead of the whole organisation. By default, a webhook subscription fires for **every** matching event across your organisation. Entity-level filtering lets you narrow delivery to specific entities — for example, only fire `challenge-completion` for one flagship challenge, or only fire `reward-claim` for a particular reward asset. Entity filters are optional. If you omit them, the webhook fires for all entities in the organisation. ## Why filter by entity? * **Reduce handler load**: only process events your downstream system cares about. * **Per-partner webhooks**: point different webhook endpoints at different challenges (e.g. one challenge powers a partner's co-marketing flow, the rest stay internal). * **Avoid accidental fan-out**: when you subscribe to a broad event like `reward-distribution`, filters stop you from processing unrelated rewards. ## Configure a filter Entity filters are set per subscription. In the admin panel, open a webhook config, select an event, and pick the entities from the dropdown: Via the Admin API, add an `entityFilters` map on the subscription: ```bash theme={null} curl -X PUT "https://{subdomain}.nudj.cx/api/v2/admin/webhooks/configs" \ -H "x-api-token: YOUR_ADMIN_API_TOKEN" \ -H "Content-Type: application/json" \ -d '[ { "url": "https://your-app.com/webhooks/nudj", "httpMethod": "POST", "isEnabled": true, "maxRetries": 3, "events": ["challenge-completion", "reward-claim"], "entityFilters": { "challenge-completion": ["65a0b1c2d3e4f5a6b7c8d9e0"], "reward-claim": ["65a0b1c2d3e4f5a6b7c8d9e1", "65a0b1c2d3e4f5a6b7c8d9e2"] } } ]' ``` Entity IDs must belong to entities visible to the token's organisation. Filtering by an ID from a different org is rejected. ## Event → entity mapping Different events key off different entity types: | Event | Entity Type | `eventSourceId` on payload | | - | - | - | | `challenge-started`, `challenge-completion` | Challenge | Challenge ID | | `achievement-completion` | Achievement | Achievement ID | | `reward-claim`, `reward-redemption`, `reward-distribution` | Reward | Reward ID | | `action-participation` | Action | Action ID | | `auth.email`, `streak-extended` | *(not filterable — organisation-wide)* | N/A | Check the [Event Catalog](/developer/webhooks/event-catalog) for the full list. ## Editing filters Filters can be changed any time — changes take effect immediately for new events. Existing in-flight deliveries (already queued) finish using the filter state at the time they were enqueued. ## Related Every event Nudj can fire and the entities it attaches to. Build your handler to trust the filtered event stream. # Implementation Guide Source: https://docs.nudj.cx/developer/webhooks/implementation Step-by-step guide to implement webhook handling with code examples in multiple languages ## Overview This guide walks you through implementing webhook endpoints to receive real-time notifications from the Nudj platform. You'll learn how to configure webhooks, handle incoming requests, and implement best practices for production systems. ## Prerequisites You need Admin API credentials to configure webhooks Webhook URLs must use HTTPS in production A system to handle and process incoming events Mechanism to verify webhook authenticity ## Step 1: Configure Your Webhook First, use the Admin API to configure your webhook endpoint: ```bash cURL theme={null} curl -X PUT "https://{subdomain}.nudj.cx/api/v2/admin/webhooks/configs" \ -H "Content-Type: application/json" \ -H "x-api-token: YOUR_ADMIN_API_TOKEN" \ -d '[ { "events": [ "challenge-completion", "achievement-completion", "reward-claim" ], "url": "https://your-app.com/webhooks/nudj", "httpMethod": "POST", "isEnabled": true, "maxRetries": 3, "headers": { "Authorization": "Bearer your-webhook-secret", "X-Webhook-Source": "nudj-platform" } } ]' ``` ```javascript JavaScript (Node.js) theme={null} const response = await fetch(`https://${subdomain}.nudj.cx/api/v2/admin/webhooks/configs`, { method: 'PUT', headers: { 'Content-Type': 'application/json', 'x-api-token': process.env.NUDJ_ADMIN_API_TOKEN }, body: JSON.stringify([ { events: [ 'challenge-completion', 'achievement-completion', 'reward-claim' ], url: 'https://your-app.com/webhooks/nudj', httpMethod: 'POST', isEnabled: true, maxRetries: 3, headers: { 'Authorization': `Bearer ${process.env.WEBHOOK_SECRET}`, 'X-Webhook-Source': 'nudj-platform' } } ]) }); ``` ```python Python theme={null} import requests import os response = requests.put( f"https://{subdomain}.nudj.cx/api/v2/admin/webhooks/configs", headers={ "Content-Type": "application/json", "x-api-token": os.getenv("NUDJ_ADMIN_API_TOKEN") }, json=[ { "events": [ "challenge-completion", "achievement-completion", "reward-claim" ], "url": "https://your-app.com/webhooks/nudj", "httpMethod": "POST", "isEnabled": True, "maxRetries": 3, "headers": { "Authorization": f"Bearer {os.getenv('WEBHOOK_SECRET')}", "X-Webhook-Source": "nudj-platform" } } ] ) ``` ## Step 2: Implement Webhook Endpoint Create an endpoint to receive and process webhook events: ```javascript Node.js (Express) theme={null} const express = require('express'); const crypto = require('crypto'); const app = express(); // Middleware to parse JSON with raw body for signature verification app.use('/webhooks', express.json({ verify: (req, res, buf, encoding) => { req.rawBody = buf; } })); // Webhook endpoint app.post('/webhooks/nudj', async (req, res) => { try { // Verify the webhook is from Nudj if (!verifyWebhookSignature(req)) { return res.status(401).send('Unauthorized'); } const event = req.body; console.log('Received webhook:', event.eventSubCategory); // Process the event await processWebhookEvent(event); // Always respond with 200 for successful processing res.status(200).send('OK'); } catch (error) { console.error('Webhook processing error:', error); res.status(500).send('Internal Server Error'); } }); function verifyWebhookSignature(req) { const expectedAuth = `Bearer ${process.env.WEBHOOK_SECRET}`; const receivedAuth = req.get('Authorization'); const webhookSource = req.get('X-Webhook-Source'); return receivedAuth === expectedAuth && webhookSource === 'nudj-platform'; } async function processWebhookEvent(event) { switch (event.eventSubCategory) { case 'challenge-completion': await handleChallengeCompletion(event); break; case 'achievement-completion': await handleAchievementCompletion(event); break; case 'reward-claim': await handleRewardClaim(event); break; default: console.log('Unhandled event type:', event.eventSubCategory); } } async function handleChallengeCompletion(event) { const { userId, eventSourceId, payload } = event; // Example: Send congratulatory email await sendEmail(userId, { subject: 'Challenge Completed!', template: 'challenge-completion', data: { challengeName: payload.challengeName, pointsEarned: payload.pointsEarned } }); // Example: Update external system await updateExternalProfile(userId, { lastChallengeCompleted: eventSourceId, totalChallengesCompleted: await getChallengeCount(userId) + 1 }); } app.listen(3000, () => { console.log('Webhook server listening on port 3000'); }); ``` ```python Python (Flask) theme={null} from flask import Flask, request, jsonify import os import hashlib import hmac import json app = Flask(__name__) @app.route('/webhooks/nudj', methods=['POST']) def handle_webhook(): try: # Verify the webhook is from Nudj if not verify_webhook_signature(request): return jsonify({'error': 'Unauthorized'}), 401 event = request.json print(f"Received webhook: {event.get('eventSubCategory')}") # Process the event process_webhook_event(event) return jsonify({'status': 'success'}), 200 except Exception as e: print(f"Webhook processing error: {e}") return jsonify({'error': 'Internal Server Error'}), 500 def verify_webhook_signature(request): expected_auth = f"Bearer {os.getenv('WEBHOOK_SECRET')}" received_auth = request.headers.get('Authorization') webhook_source = request.headers.get('X-Webhook-Source') return (received_auth == expected_auth and webhook_source == 'nudj-platform') def process_webhook_event(event): event_type = event.get('eventSubCategory') handlers = { 'challenge-completion': handle_challenge_completion, 'achievement-completion': handle_achievement_completion, 'reward-claim': handle_reward_claim } handler = handlers.get(event_type) if handler: handler(event) else: print(f"Unhandled event type: {event_type}") def handle_challenge_completion(event): user_id = event.get('userId') payload = event.get('payload', {}) # Example: Send congratulatory message send_notification(user_id, { 'type': 'challenge_completed', 'message': f"Congratulations! You completed {payload.get('challengeName')}", 'points_earned': payload.get('pointsEarned') }) # Example: Update user stats update_user_stats(user_id, 'challenges_completed', 1) def handle_achievement_completion(event): # Implementation for achievement completion pass def handle_reward_claim(event): # Implementation for reward claim pass if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) ``` ```ruby Ruby (Sinatra) theme={null} require 'sinatra' require 'json' set :port, 4567 post '/webhooks/nudj' do begin # Verify the webhook is from Nudj unless verify_webhook_signature(request) halt 401, 'Unauthorized' end # Parse the JSON payload event = JSON.parse(request.body.read) puts "Received webhook: #{event['eventSubCategory']}" # Process the event process_webhook_event(event) status 200 body 'OK' rescue => e puts "Webhook processing error: #{e.message}" status 500 body 'Internal Server Error' end end def verify_webhook_signature(request) expected_auth = "Bearer #{ENV['WEBHOOK_SECRET']}" received_auth = request.env['HTTP_AUTHORIZATION'] webhook_source = request.env['HTTP_X_WEBHOOK_SOURCE'] received_auth == expected_auth && webhook_source == 'nudj-platform' end def process_webhook_event(event) case event['eventSubCategory'] when 'challenge-completion' handle_challenge_completion(event) when 'achievement-completion' handle_achievement_completion(event) when 'reward-claim' handle_reward_claim(event) else puts "Unhandled event type: #{event['eventSubCategory']}" end end def handle_challenge_completion(event) user_id = event['userId'] payload = event['payload'] # Example: Send notification send_notification(user_id, { type: 'challenge_completed', message: "You completed #{payload['challengeName']}!", points_earned: payload['pointsEarned'] }) end ``` ```php PHP theme={null} getMessage()); http_response_code(500); echo 'Internal Server Error'; } function verifyWebhookSignature() { $expectedAuth = 'Bearer ' . $_ENV['WEBHOOK_SECRET']; $receivedAuth = $_SERVER['HTTP_AUTHORIZATION'] ?? ''; $webhookSource = $_SERVER['HTTP_X_WEBHOOK_SOURCE'] ?? ''; return $receivedAuth === $expectedAuth && $webhookSource === 'nudj-platform'; } function processWebhookEvent($event) { switch ($event['eventSubCategory']) { case 'challenge-completion': handleChallengeCompletion($event); break; case 'achievement-completion': handleAchievementCompletion($event); break; case 'reward-claim': handleRewardClaim($event); break; default: error_log('Unhandled event type: ' . $event['eventSubCategory']); } } function handleChallengeCompletion($event) { $userId = $event['userId']; $payload = $event['payload']; // Example: Log the completion error_log("User {$userId} completed challenge: " . $payload['challengeName']); // Example: Send notification sendNotification($userId, [ 'type' => 'challenge_completed', 'message' => "Congratulations! You completed {$payload['challengeName']}", 'points_earned' => $payload['pointsEarned'] ]); } function sendNotification($userId, $data) { // Implementation for sending notifications error_log("Sending notification to user {$userId}: " . json_encode($data)); } ?> ``` ```go Go (Gin) theme={null} package main import ( "encoding/json" "fmt" "log" "net/http" "os" "github.com/gin-gonic/gin" ) type WebhookEvent struct { ID string `json:"id"` EventCategory string `json:"eventCategory"` EventSubCategory string `json:"eventSubCategory"` OrganisationID string `json:"organisationId"` CommunityID string `json:"communityId"` UserID string `json:"userId"` EventSourceID string `json:"eventSourceId"` Payload interface{} `json:"payload"` Tags []string `json:"tags"` CreatedAt string `json:"createdAt"` } func main() { r := gin.Default() r.POST("/webhooks/nudj", handleWebhook) log.Println("Webhook server starting on :8080") r.Run(":8080") } func handleWebhook(c *gin.Context) { // Verify webhook signature if !verifyWebhookSignature(c) { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) return } var event WebhookEvent if err := c.ShouldBindJSON(&event); err != nil { log.Printf("Error parsing webhook: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON"}) return } log.Printf("Received webhook: %s", event.EventSubCategory) // Process the event if err := processWebhookEvent(event); err != nil { log.Printf("Error processing webhook: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Processing failed"}) return } c.JSON(http.StatusOK, gin.H{"status": "success"}) } func verifyWebhookSignature(c *gin.Context) bool { expectedAuth := fmt.Sprintf("Bearer %s", os.Getenv("WEBHOOK_SECRET")) receivedAuth := c.GetHeader("Authorization") webhookSource := c.GetHeader("X-Webhook-Source") return receivedAuth == expectedAuth && webhookSource == "nudj-platform" } func processWebhookEvent(event WebhookEvent) error { switch event.EventSubCategory { case "challenge-completion": return handleChallengeCompletion(event) case "achievement-completion": return handleAchievementCompletion(event) case "reward-claim": return handleRewardClaim(event) default: log.Printf("Unhandled event type: %s", event.EventSubCategory) return nil } } func handleChallengeCompletion(event WebhookEvent) error { payload, _ := json.Marshal(event.Payload) log.Printf("User %s completed a challenge: %s", event.UserID, string(payload)) // Add your business logic here // - Send notifications // - Update external systems // - Trigger other processes return nil } func handleAchievementCompletion(event WebhookEvent) error { // Implementation for achievement completion return nil } func handleRewardClaim(event WebhookEvent) error { // Implementation for reward claim return nil } ``` ## Step 3: Implement Event Handlers Create specific handlers for different event types: ```javascript Challenge Completion Handler theme={null} async function handleChallengeCompletion(event) { const { userId, communityId, eventSourceId, payload } = event; const challengeId = eventSourceId; try { // 1. Award bonus points for fast completion if (payload.timeToComplete < 3600) { // Less than 1 hour await awardBonusPoints(userId, 50, 'Fast challenge completion'); } // 2. Update user statistics await updateUserStats(userId, { challengesCompleted: 1, totalPointsEarned: payload.pointsEarned, totalXpEarned: payload.xpEarned }); // 3. Check for achievement eligibility const challengeCount = await getUserChallengeCount(userId); if (challengeCount === 10) { await triggerAchievement(userId, 'challenge-master'); } // 4. Send personalized notification await sendNotification(userId, { type: 'challenge_completed', title: 'Challenge Complete!', message: `Great job completing "${payload.challengeName}"!`, data: { pointsEarned: payload.pointsEarned, xpEarned: payload.xpEarned, completionTime: formatDuration(payload.timeToComplete) } }); // 5. Update leaderboards await updateLeaderboard(communityId, userId, { challengesCompleted: 1, pointsEarned: payload.pointsEarned }); } catch (error) { console.error(`Error handling challenge completion for user ${userId}:`, error); throw error; } } ``` ```javascript Reward Claim Handler theme={null} async function handleRewardClaim(event) { const { userId, eventSourceId, payload } = event; const rewardId = eventSourceId; try { // 1. Log the reward claim for analytics await logEvent('reward_claimed', { userId, rewardId, rewardName: payload.rewardName, rewardType: payload.rewardType, timestamp: event.createdAt }); // 2. Handle different reward types switch (payload.rewardType) { case 'asset': await handleAssetReward(userId, rewardId, payload); break; case 'entry': await handleEntryReward(userId, rewardId, payload); break; default: console.warn('Unknown reward type:', payload.rewardType); } // 3. Send confirmation notification await sendNotification(userId, { type: 'reward_claimed', title: 'Reward Claimed!', message: `You've successfully claimed: ${payload.rewardName}`, data: payload }); // 4. Update user profile with reward await updateUserProfile(userId, { rewardsClaimed: 1, lastRewardClaimed: { id: rewardId, name: payload.rewardName, claimedAt: event.createdAt } }); } catch (error) { console.error(`Error handling reward claim for user ${userId}:`, error); throw error; } } async function handleAssetReward(userId, rewardId, payload) { // Asset rewards are permanent items (badges, titles, etc.) await addUserAsset(userId, { rewardId, name: payload.rewardName, type: 'reward_asset', acquiredAt: new Date().toISOString() }); } async function handleEntryReward(userId, rewardId, payload) { // Entry rewards are chances to win (lottery entries, etc.) await addUserEntry(userId, { rewardId, name: payload.rewardName, type: 'reward_entry', enteredAt: new Date().toISOString() }); } ``` ```javascript Variable Captured Handler theme={null} async function handleVariableCaptured(event) { const { userId, payload } = event; try { // 1. Store the captured variable await storeUserVariable(userId, { variableId: payload.variableId, variableName: payload.variableName, value: payload.value, capturedAt: payload.capturedAt }); // 2. Trigger personalization logic await updateUserPersonalization(userId, { [payload.variableName]: payload.value }); // 3. Check for completion of user profile const profileCompletion = await calculateProfileCompletion(userId); if (profileCompletion === 100) { await triggerAchievement(userId, 'profile-complete'); } // 4. Update segmentation await updateUserSegmentation(userId, { variableName: payload.variableName, value: payload.value }); } catch (error) { console.error(`Error handling variable capture for user ${userId}:`, error); throw error; } } ``` ## Step 4: Error Handling and Retry Logic Implement robust error handling to ensure reliable webhook processing: ```javascript Error Handling theme={null} const MAX_RETRIES = 3; const RETRY_DELAY = 1000; // 1 second async function processWebhookEvent(event) { let attempt = 1; while (attempt <= MAX_RETRIES) { try { await processEvent(event); return; // Success, exit retry loop } catch (error) { console.error(`Attempt ${attempt} failed:`, error); if (attempt === MAX_RETRIES) { // Final attempt failed, handle accordingly await handleFinalFailure(event, error); throw error; } // Wait before retrying await sleep(RETRY_DELAY * attempt); attempt++; } } } async function handleFinalFailure(event, error) { // Log to error monitoring service console.error('Webhook processing failed after all retries:', { eventId: event.id, eventType: event.eventSubCategory, error: error.message, stack: error.stack }); // Store in dead letter queue for manual review await storeInDeadLetterQueue(event, error); // Alert operations team await sendAlert('webhook-processing-failed', { eventId: event.id, eventType: event.eventSubCategory, error: error.message }); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } ``` ```javascript Idempotency theme={null} // Store processed webhook IDs to prevent duplicate processing const processedWebhooks = new Set(); app.post('/webhooks/nudj', async (req, res) => { try { const event = req.body; const webhookId = event.id; // Check if we've already processed this webhook if (processedWebhooks.has(webhookId)) { console.log(`Webhook ${webhookId} already processed, ignoring`); return res.status(200).send('OK'); } // Verify the webhook if (!verifyWebhookSignature(req)) { return res.status(401).send('Unauthorized'); } // Process the event await processWebhookEvent(event); // Mark as processed processedWebhooks.add(webhookId); res.status(200).send('OK'); } catch (error) { console.error('Webhook processing error:', error); res.status(500).send('Internal Server Error'); } }); ``` ## Step 5: Testing Your Implementation Test your webhook implementation thoroughly: ```javascript Testing Script theme={null} const axios = require('axios'); // Test webhook endpoint locally async function testWebhook() { const testPayload = { id: 'test-webhook-' + Date.now(), eventCategory: 'challenge', eventSubCategory: 'challenge-completion', organisationId: '60f7b8c8e7d4a2b1c9e3f4a1', communityId: '60f7b8c8e7d4a2b1c9e3f4a2', userId: '60f7b8c8e7d4a2b1c9e3f4a3', eventSourceId: '60f7b8c8e7d4a2b1c9e3f4a4', payload: { challengeId: '60f7b8c8e7d4a2b1c9e3f4a4', challengeName: 'Test Challenge', completedAt: new Date().toISOString(), timeToComplete: 1800, pointsEarned: 100, xpEarned: 50 }, createdAt: new Date().toISOString() }; try { const response = await axios.post('http://localhost:3000/webhooks/nudj', testPayload, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.WEBHOOK_SECRET}`, 'X-Webhook-Source': 'nudj-platform' } }); console.log('Test webhook successful:', response.status); } catch (error) { console.error('Test webhook failed:', error.message); } } testWebhook(); ``` ```bash Testing with ngrok theme={null} # Install ngrok for local testing npm install -g ngrok # Start your local webhook server node webhook-server.js & # Expose local server via ngrok ngrok http 3000 # Use the ngrok URL in your webhook configuration # Example: https://abc123.ngrok.io/webhooks/nudj ``` ## Step 6: Production Deployment ### Security Checklist * Use valid SSL certificates * Enable HSTS headers * Disable HTTP fallback * Use strong cipher suites * Verify webhook signatures * Use environment variables for secrets * Implement rate limiting * Add IP allowlisting if needed * Implement retry logic * Use dead letter queues * Add comprehensive logging * Set up monitoring and alerts * Process webhooks asynchronously * Implement connection pooling * Add caching where appropriate * Monitor response times ### Monitoring and Logging ```javascript Structured Logging theme={null} const winston = require('winston'); const logger = winston.createLogger({ level: 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json() ), defaultMeta: { service: 'webhook-handler' }, transports: [ new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }), new winston.transports.Console({ format: winston.format.simple() }) ] }); app.post('/webhooks/nudj', async (req, res) => { const startTime = Date.now(); const event = req.body; logger.info('Webhook received', { webhookId: event.id, eventType: event.eventSubCategory, userId: event.userId, timestamp: event.createdAt }); try { await processWebhookEvent(event); logger.info('Webhook processed successfully', { webhookId: event.id, processingTime: Date.now() - startTime }); res.status(200).send('OK'); } catch (error) { logger.error('Webhook processing failed', { webhookId: event.id, error: error.message, stack: error.stack, processingTime: Date.now() - startTime }); res.status(500).send('Internal Server Error'); } }); ``` ```javascript Health Checks theme={null} app.get('/health', (req, res) => { res.status(200).json({ status: 'healthy', timestamp: new Date().toISOString(), uptime: process.uptime(), version: process.env.APP_VERSION || '1.0.0' }); }); app.get('/webhooks/health', (req, res) => { // Check if webhook processing is healthy const isHealthy = checkWebhookHealth(); res.status(isHealthy ? 200 : 503).json({ status: isHealthy ? 'healthy' : 'unhealthy', webhookProcessor: isHealthy, lastProcessedWebhook: getLastProcessedWebhookTime(), timestamp: new Date().toISOString() }); }); ``` ## Common Integration Patterns ### Real-time Notifications ```javascript Push Notifications theme={null} async function sendPushNotification(userId, event) { const userDevices = await getUserDevices(userId); const notification = { title: getNotificationTitle(event), body: getNotificationMessage(event), data: { eventType: event.eventSubCategory, eventId: event.id, deepLink: generateDeepLink(event) } }; for (const device of userDevices) { await pushService.send(device.token, notification); } } function getNotificationTitle(event) { const titles = { 'challenge-completion': 'Challenge Complete! 🏆', 'achievement-completion': 'Achievement Unlocked! 🎉', 'reward-claim': 'Reward Claimed! 🎁', 'streak-extended': 'Streak Extended! 🔥' }; return titles[event.eventSubCategory] || 'Nudj Notification'; } ``` ### Email Marketing Integration ```javascript Email Campaigns theme={null} async function triggerEmailCampaign(event) { const user = await getUser(event.userId); const campaignData = { email: user.email, templateId: getEmailTemplate(event.eventSubCategory), personalizations: [{ email: user.email, dynamic_template_data: { firstName: user.firstName, eventData: event.payload, communityName: await getCommunityName(event.communityId) } }] }; await emailService.send(campaignData); } function getEmailTemplate(eventType) { const templates = { 'challenge-completion': 'challenge_completed_v2', 'achievement-completion': 'achievement_unlocked_v2', 'reward-claim': 'reward_claimed_v1' }; return templates[eventType] || 'generic_notification_v1'; } ``` ### Analytics Integration ```javascript Analytics Tracking theme={null} async function trackAnalyticsEvent(event) { const analyticsEvent = { userId: event.userId, eventName: `nudj_${event.eventSubCategory}`, properties: { organisationId: event.organisationId, communityId: event.communityId, eventSourceId: event.eventSourceId, timestamp: event.createdAt, ...event.payload } }; // Send to multiple analytics platforms await Promise.all([ analytics.track(analyticsEvent), mixpanel.track(analyticsEvent.userId, analyticsEvent.eventName, analyticsEvent.properties), amplitude.logEvent(analyticsEvent) ]); } ``` ## Best Practices Return HTTP 200 within 10 seconds. Process heavy workloads asynchronously. Use webhook IDs to implement idempotent processing and prevent duplicate handling. Always verify webhook authenticity and validate payload structure before processing. Track processing times, error rates, and webhook delivery success rates. Handle transient failures gracefully with exponential backoff retry logic. Maintain detailed logs for debugging and monitoring webhook processing. *** ## Next Steps See every available webhook event and its specific payload. Learn how to trigger test events to verify your endpoint. # Delivery Logs & Retries Source: https://docs.nudj.cx/developer/webhooks/logs Inspect webhook delivery attempts, debug failures, and manually retry failed events. Every webhook Nudj sends — successful or not — is persisted to a dedicated log collection. You can query the log via the Admin API, or inspect it in the admin panel and re-trigger failed deliveries with one click. Delivery logs are organisation-scoped. You need an Admin-tier API token to read them. ## When are logs written? Nudj writes a log record each time it **attempts** to deliver a webhook: * On the initial attempt, whether the endpoint returns `2xx` or not. * On each automatic retry (up to your configured `maxRetries`). * On any manual retry triggered from the admin panel or API. ## Log record shape ```json theme={null} { "id": "652f8a3c1e4f7d0012a3b4c5", "webhookConfigId": "650c9e1a...", "organisationId": "5f7d1e2a...", "eventSubCategory": "challenge-completion", "url": "https://your-app.com/webhooks/nudj", "httpMethod": "POST", "requestHeaders": { "Authorization": "Bearer ***", "X-Webhook-Source": "nudj-platform" }, "requestBody": { "id": "...", "eventCategory": "challenge", "userId": "...", "payload": { /* ... */ } }, "responseStatus": 500, "responseHeaders": { "content-type": "text/plain" }, "responseBody": "Internal Server Error", "attempt": 2, "success": false, "errorMessage": "HTTP 500: Internal Server Error", "createdAt": "2026-03-14T10:21:45.102Z" } ``` Secret values in `requestHeaders` are redacted in the stored record. ## Query logs via the API Use the Admin API endpoint to page through recent delivery attempts: ```bash theme={null} curl -X GET "https://{subdomain}.nudj.cx/api/v2/admin/webhooks/logs?limit=50" \ -H "x-api-token: YOUR_ADMIN_API_TOKEN" ``` See [`GET /webhooks/logs`](/api-reference/admin/get-webhooks-logs) for filter options (by `eventSubCategory`, `success`, date range, etc.). ## Retry a failed delivery In the admin panel, navigate to **Settings → Organisation → Webhooks → Delivery Logs** and click the **Retry** icon next to any failed row. Nudj will resend the exact same payload to your endpoint and write a fresh log entry with `attempt: N+1`. Manual retries do not count against your configured `maxRetries`. Don't loop retries programmatically — fix the root cause first, then retry. ## Use cases * **Debugging a broken handler**: compare `requestBody` to what your handler received. Check if a reverse proxy, payload size limit, or JSON parser is stripping fields. * **Replaying events after downtime**: your endpoint was down for 10 minutes? Filter logs by `success: false` and retry each one. * **Auditing**: prove to internal stakeholders that a specific event was delivered with the exact payload and response. ## Retry semantics Automatic retries use a fixed 5-second delay and stop after the webhook's `maxRetries` value (default 3). There is no exponential backoff today — if your endpoint is flapping, use the manual retry flow once the root cause is fixed instead of relying on auto-retry. Every retry attempt — automatic or manual — gets its own log entry, so you can trace the exact delivery timeline per event. ## Related Diagnose common delivery failures. Build a compliant handler from scratch. # Overview Source: https://docs.nudj.cx/developer/webhooks/overview Real-time event notifications from the Nudj platform to your applications ## What are Webhooks? Webhooks allow your application to receive real-time notifications when specific events occur within the Nudj platform. Instead of constantly polling our API for updates, webhooks push data to your application immediately when something happens. When an event occurs (such as a user completing a challenge or earning a reward), Nudj sends an HTTP request to your configured webhook endpoint with event details. **Interactive Testing Available**: You can configure and test webhooks directly from this documentation using our [interactive API playground](/developer/webhooks/testing). No need to write code to get started! ## Key Features Receive instant notifications when events occur, eliminating the need for polling Built-in retry logic with configurable attempts ensures your webhook receives the data Choose which events to receive and customize HTTP methods and headers Custom headers support for secure webhook authentication ## How Webhooks Work 1. **Event Occurs**: A user action or system event triggers within your Nudj community 2. **Webhook Triggered**: Nudj identifies configured webhooks for the event type 3. **HTTP Request Sent**: A POST request is sent to your webhook URL with a JSON payload 4. **Response Processed**: Your application processes the webhook data 5. **Retry if Needed**: If delivery fails, Nudj automatically retries based on your configuration ## Webhook Configuration Webhooks are configured at the **organisation level**, meaning all communities within an organisation share the same webhook configuration. You can manage webhooks from the Admin Panel under **Settings → Organisation → Webhooks**. ### Basic Configuration | Field | Description | Required | | - | - | - | | **URL** | The endpoint where webhook payloads will be sent (HTTPS recommended) | Yes | | **Events** | Specific event types you want to receive notifications for | Yes | | **Max Retries** | Number of delivery attempts on failure (default: 3) | No | | **Enabled** | Toggle to enable/disable the webhook without deleting it | No | | **Created At** | Timestamp showing when the webhook was created | Auto | All webhooks use the **POST** HTTP method. Your endpoint should accept POST requests with a JSON payload. ### Custom Headers (Advanced) You can configure custom HTTP headers for webhooks that require authentication. This is useful when your webhook endpoint requires: * **API Keys**: `x-api-key: YOUR_SECRET_KEY` * **Bearer Tokens**: `Authorization: Bearer YOUR_TOKEN` * **Custom Authentication**: Any header your endpoint expects Headers are configured in the **Advanced Settings** section when creating a webhook. Each header is a key-value pair that will be sent with every webhook request. **Example Header Configuration:** ```json theme={null} { "x-api-key": "7AFDD775334D43A2937D461997408414", "Content-Type": "application/json" } ``` ### Event Filtering You can configure webhooks to receive notifications for specific event categories: * **User Activity**: Challenge completions, action participations, social interactions * **Achievements & Rewards**: Achievement completions, reward claims and redemptions * **Content Engagement**: Post likes, comments, community visits * **User Profile Changes**: Profile updates, variable captures * **System Events**: Custom events, referral usage ## Quick Start Use the [interactive API playground](/developer/webhooks/testing) to configure your webhook: ```bash theme={null} PUT https://{subdomain}.nudj.cx/api/v2/admin/webhooks/configs ``` Try the configuration directly in our [testing guide](/developer/webhooks/testing) with webhook.site or ngrok Create an endpoint to receive webhook payloads with proper authentication Trigger test events and monitor webhook delivery in real-time ## Security Considerations Always validate webhook requests in production to ensure they're coming from Nudj * **Custom Headers**: Use authentication headers to verify request authenticity * **HTTPS Only**: Always use HTTPS endpoints for webhook URLs * **IP Allowlisting**: Consider restricting webhook traffic to known Nudj IP ranges * **Request Validation**: Verify the request structure matches expected webhook format ## How Webhooks Are Delivered ### What You Need to Know * **Success**: Return any 2xx status code (200, 201, 204, etc.) * **Retries**: Failed webhooks retry up to 3 times with 5-second delays * **Timeout**: Respond within 5 seconds to avoid timeouts * **Order**: Events are delivered in the order they occur ## Supported Events Nudj supports webhook notifications for key platform events. See our [Event Catalog](/developer/webhooks/event-catalog) for the complete list of available events and their payload structures. ### Event Categories * **Challenge Events**: `challenge.started`, `challenge.completed` * **Achievement Events**: `achievement.completion` * **Reward Events**: `reward.claim`, `reward.redemption`, `reward.distribution` * **User Events**: Profile updates, community joins * **Social Events**: Post interactions, comments, shares * **Custom Events**: Organization-specific custom events ## Where to Start Configure and test webhooks interactively See all available events with examples Ready-to-use integrations for popular platforms Step-by-step implementation guide ## Need Help? If you need assistance setting up webhooks or have questions about specific events, please contact our support team at [support@nudj.cx](mailto:support@nudj.cx). # Recipe Library Source: https://docs.nudj.cx/developer/webhooks/recipes Ready-to-use webhook integration patterns and code examples ## Recipe Library Overview Pre-built webhook integration patterns for common use cases. Each recipe includes complete code examples and deployment instructions. ## Recipe: Slack Notification Bot Send real-time Slack notifications when users complete challenges or earn rewards. ### Prerequisites * Slack workspace with incoming webhooks enabled * Slack webhook URL from your workspace settings ### Configuration 1. Get your Slack webhook URL from Slack App settings 2. Configure Nudj webhook to point to your handler 3. Deploy the handler code below ```javascript theme={null} const express = require('express'); const axios = require('axios'); const app = express(); const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL; const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; app.post('/webhooks/nudj', express.json(), async (req, res) => { // Verify webhook authenticity if (req.headers['x-api-key'] !== WEBHOOK_SECRET) { return res.status(401).json({ error: 'Unauthorized' }); } const event = req.body; try { // Handle different event types switch (event.eventSubCategory) { case 'challenge-completion': await sendSlackMessage({ text: `🎯 Challenge Completed!`, blocks: [{ type: 'section', text: { type: 'mrkdwn', text: `*User:* ${event.userId}\n*Challenge:* ${event.eventSourceId}\n*Time:* ${event.createdAt}` } }] }); break; case 'reward-distribution': const { earnedPoints, earnedXp } = event.payload; await sendSlackMessage({ text: `🎁 Rewards Distributed!`, blocks: [{ type: 'section', text: { type: 'mrkdwn', text: `*User:* ${event.userId}\n*Points:* ${earnedPoints}\n*XP:* ${earnedXp}` } }] }); break; case 'achievement-completion': await sendSlackMessage({ text: `🏆 Achievement Unlocked!`, blocks: [{ type: 'section', text: { type: 'mrkdwn', text: `*User:* ${event.userId}\n*Achievement:* ${event.payload.achievementName}` } }] }); break; } res.status(200).json({ success: true }); } catch (error) { console.error('Slack notification failed:', error); res.status(200).json({ success: true }); // Return 200 to prevent retries } }); async function sendSlackMessage(message) { await axios.post(SLACK_WEBHOOK_URL, message); } app.listen(3000, () => { console.log('Webhook handler running on port 3000'); }); ``` ```python theme={null} from flask import Flask, request, jsonify import requests import os from datetime import datetime app = Flask(__name__) SLACK_WEBHOOK_URL = os.environ.get('SLACK_WEBHOOK_URL') WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET') @app.route('/webhooks/nudj', methods=['POST']) def handle_webhook(): # Verify webhook authenticity if request.headers.get('X-Api-Key') != WEBHOOK_SECRET: return jsonify({'error': 'Unauthorized'}), 401 event = request.json try: event_type = event.get('eventSubCategory') if event_type == 'challenge-completion': send_slack_message({ 'text': '🎯 Challenge Completed!', 'blocks': [{ 'type': 'section', 'text': { 'type': 'mrkdwn', 'text': f"*User:* {event.get('userId')}\n" f"*Challenge:* {event.get('eventSourceId')}\n" f"*Time:* {event.get('createdAt')}" } }] }) elif event_type == 'reward-distribution': payload = event.get('payload', {}) send_slack_message({ 'text': '🎁 Rewards Distributed!', 'blocks': [{ 'type': 'section', 'text': { 'type': 'mrkdwn', 'text': f"*User:* {event.get('userId')}\n" f"*Points:* {payload.get('earnedPoints')}\n" f"*XP:* {payload.get('earnedXp')}" } }] }) return jsonify({'success': True}), 200 except Exception as e: print(f"Error processing webhook: {e}") return jsonify({'success': True}), 200 # Return 200 to prevent retries def send_slack_message(message): requests.post(SLACK_WEBHOOK_URL, json=message) if __name__ == '__main__': app.run(port=3000) ``` ## Recipe: Email Notification System Send personalized emails for important user events using SendGrid. ### Prerequisites * SendGrid account and API key * Email templates configured in SendGrid * Verified sender domain ### Email Templates Needed * Challenge completion * Reward earned * Achievement unlocked * Weekly summary ```javascript theme={null} const express = require('express'); const sgMail = require('@sendgrid/mail'); const app = express(); sgMail.setApiKey(process.env.SENDGRID_API_KEY); const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; // Template IDs from SendGrid const TEMPLATES = { 'challenge-completion': 'd-challenge-complete-template-id', 'reward-distribution': 'd-reward-earned-template-id', 'achievement-completion': 'd-achievement-template-id', 'streak-extended': 'd-streak-template-id' }; app.post('/webhooks/nudj', express.json(), async (req, res) => { if (req.headers['x-api-key'] !== WEBHOOK_SECRET) { return res.status(401).json({ error: 'Unauthorized' }); } const event = req.body; try { const templateId = TEMPLATES[event.eventSubCategory]; if (!templateId) { return res.status(200).json({ success: true }); } // Extract user email from payload (reward-distribution includes it) let userEmail; if (event.eventSubCategory === 'reward-distribution') { userEmail = event.payload?.user?.email; } else { // You'd need to look up user email from your database userEmail = await getUserEmail(event.userId); } if (userEmail) { await sendEmail(userEmail, templateId, event); } res.status(200).json({ success: true }); } catch (error) { console.error('Email send failed:', error); res.status(200).json({ success: true }); } }); async function sendEmail(to, templateId, eventData) { const msg = { to, from: 'notifications@yourcompany.com', templateId, dynamicTemplateData: { userId: eventData.userId, eventType: eventData.eventSubCategory, timestamp: eventData.createdAt, ...eventData.payload } }; await sgMail.send(msg); } async function getUserEmail(userId) { // Implement your user lookup logic here // This would query your database or call your API return null; // Placeholder } app.listen(3000, () => { console.log('Email webhook handler running on port 3000'); }); ``` ```python theme={null} from flask import Flask, request, jsonify from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import Mail, Email, To import os app = Flask(__name__) sg = SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET') TEMPLATES = { 'challenge-completion': 'd-challenge-complete-template-id', 'reward-distribution': 'd-reward-earned-template-id', 'achievement-completion': 'd-achievement-template-id', 'streak-extended': 'd-streak-template-id' } @app.route('/webhooks/nudj', methods=['POST']) def handle_webhook(): if request.headers.get('X-Api-Key') != WEBHOOK_SECRET: return jsonify({'error': 'Unauthorized'}), 401 event = request.json try: template_id = TEMPLATES.get(event.get('eventSubCategory')) if not template_id: return jsonify({'success': True}), 200 # Get user email user_email = None if event.get('eventSubCategory') == 'reward-distribution': user_email = event.get('payload', {}).get('user', {}).get('email') else: user_email = get_user_email(event.get('userId')) if user_email: send_email(user_email, template_id, event) return jsonify({'success': True}), 200 except Exception as e: print(f"Error: {e}") return jsonify({'success': True}), 200 def send_email(to_email, template_id, event_data): message = Mail( from_email='notifications@yourcompany.com', to_emails=to_email ) message.template_id = template_id message.dynamic_template_data = { 'userId': event_data.get('userId'), 'eventType': event_data.get('eventSubCategory'), 'timestamp': event_data.get('createdAt'), **event_data.get('payload', {}) } sg.send(message) def get_user_email(user_id): # Implement your user lookup return None if __name__ == '__main__': app.run(port=3000) ``` ## Recipe: Analytics & Data Pipeline Stream events to your data warehouse for analytics. ### Prerequisites * Data warehouse (BigQuery, Snowflake, Redshift) * Message queue (optional but recommended) ### Architecture 1. Webhook receives events 2. Events queued for processing 3. Batch insert to data warehouse 4. Analytics dashboards update ```javascript theme={null} const express = require('express'); const { BigQuery } = require('@google-cloud/bigquery'); const app = express(); const bigquery = new BigQuery(); const dataset = bigquery.dataset('nudj_events'); const table = dataset.table('webhook_events'); const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; const eventBatch = []; const BATCH_SIZE = 100; const FLUSH_INTERVAL = 5000; // 5 seconds app.post('/webhooks/nudj', express.json(), async (req, res) => { if (req.headers['x-api-key'] !== WEBHOOK_SECRET) { return res.status(401).json({ error: 'Unauthorized' }); } const event = req.body; // Add event to batch eventBatch.push({ event_id: event.id, event_category: event.eventCategory, event_subcategory: event.eventSubCategory, organisation_id: event.organisationId, community_id: event.communityId, user_id: event.userId, event_source_id: event.eventSourceId, payload: JSON.stringify(event.payload), tags: event.tags, created_at: event.createdAt, received_at: new Date().toISOString() }); // Flush batch if it's full if (eventBatch.length >= BATCH_SIZE) { await flushBatch(); } res.status(200).json({ success: true }); }); async function flushBatch() { if (eventBatch.length === 0) return; const events = [...eventBatch]; eventBatch.length = 0; // Clear batch try { await table.insert(events); console.log(`Inserted ${events.length} events to BigQuery`); } catch (error) { console.error('BigQuery insert failed:', error); // Consider adding to dead letter queue } } // Periodic flush setInterval(flushBatch, FLUSH_INTERVAL); app.listen(3000, () => { console.log('Analytics webhook handler running on port 3000'); }); ``` ```python theme={null} from flask import Flask, request, jsonify import snowflake.connector import json import os from datetime import datetime from queue import Queue import threading app = Flask(__name__) WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET') event_queue = Queue() # Snowflake connection conn = snowflake.connector.connect( user=os.environ.get('SNOWFLAKE_USER'), password=os.environ.get('SNOWFLAKE_PASSWORD'), account=os.environ.get('SNOWFLAKE_ACCOUNT'), warehouse='COMPUTE_WH', database='NUDJ_ANALYTICS', schema='EVENTS' ) @app.route('/webhooks/nudj', methods=['POST']) def handle_webhook(): if request.headers.get('X-Api-Key') != WEBHOOK_SECRET: return jsonify({'error': 'Unauthorized'}), 401 event = request.json # Queue event for processing event_queue.put({ 'EVENT_ID': event.get('id'), 'EVENT_CATEGORY': event.get('eventCategory'), 'EVENT_SUBCATEGORY': event.get('eventSubCategory'), 'ORGANISATION_ID': event.get('organisationId'), 'COMMUNITY_ID': event.get('communityId'), 'USER_ID': event.get('userId'), 'EVENT_SOURCE_ID': event.get('eventSourceId'), 'PAYLOAD': json.dumps(event.get('payload', {})), 'TAGS': json.dumps(event.get('tags', [])), 'CREATED_AT': event.get('createdAt'), 'RECEIVED_AT': datetime.utcnow().isoformat() }) return jsonify({'success': True}), 200 def process_queue(): """Background worker to process queued events""" cursor = conn.cursor() batch = [] while True: try: # Collect batch while not event_queue.empty() and len(batch) < 100: batch.append(event_queue.get()) if batch: # Insert batch into Snowflake cursor.executemany( """INSERT INTO WEBHOOK_EVENTS (EVENT_ID, EVENT_CATEGORY, EVENT_SUBCATEGORY, ORGANISATION_ID, COMMUNITY_ID, USER_ID, EVENT_SOURCE_ID, PAYLOAD, TAGS, CREATED_AT, RECEIVED_AT) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""", [tuple(event.values()) for event in batch] ) conn.commit() print(f"Inserted {len(batch)} events to Snowflake") batch.clear() except Exception as e: print(f"Error processing batch: {e}") threading.Event().wait(5) # Process every 5 seconds # Start background worker worker = threading.Thread(target=process_queue, daemon=True) worker.start() if __name__ == '__main__': app.run(port=3000) ``` ## Recipe: Zapier/Make.com Integration Connect Nudj webhooks to 1000+ apps without code. ### Creating a Zap 1. **Trigger Setup** * Choose "Webhooks by Zapier" as trigger * Select "Catch Hook" as event * Copy the webhook URL provided 2. **Configure Nudj Webhook** ```json theme={null} { "url": "https://hooks.zapier.com/hooks/catch/YOUR_ID/", "events": ["challenge-completion", "reward-distribution"], "httpMethod": "POST", "isEnabled": true } ``` 3. **Test & Map Fields** * Send test webhook from Nudj * Map fields to your action apps * Common actions: * Add row to Google Sheets * Create Salesforce lead * Send Teams message * Create Asana task ### Creating a Scenario 1. **Webhook Module** * Add "Webhooks" → "Custom webhook" * Copy the webhook URL * Add to Nudj configuration 2. **Data Processing** * Add Router for different event types * Parse JSON payload * Add filters for specific conditions 3. **Action Modules** * Google Sheets: Log events * Slack: Send notifications * Email: Send alerts * Database: Store records ### Example Scenario ``` Webhook → Router → Branch 1: Challenge Complete → Slack → Branch 2: Reward Earned → Google Sheets → Branch 3: Achievement → Email ``` ## Recipe: Database Sync Keep your database synchronized with Nudj events. ```javascript theme={null} const express = require('express'); const { Pool } = require('pg'); const app = express(); const pool = new Pool({ connectionString: process.env.DATABASE_URL }); const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; // Create events table if not exists async function initDatabase() { await pool.query(` CREATE TABLE IF NOT EXISTS nudj_events ( id VARCHAR(255) PRIMARY KEY, event_category VARCHAR(100), event_subcategory VARCHAR(100), organisation_id VARCHAR(255), community_id VARCHAR(255), user_id VARCHAR(255), event_source_id VARCHAR(255), payload JSONB, tags TEXT[], created_at TIMESTAMP, received_at TIMESTAMP DEFAULT NOW() ) `); // Create user points table await pool.query(` CREATE TABLE IF NOT EXISTS user_points ( user_id VARCHAR(255) PRIMARY KEY, total_points INTEGER DEFAULT 0, total_xp INTEGER DEFAULT 0, last_updated TIMESTAMP DEFAULT NOW() ) `); } app.post('/webhooks/nudj', express.json(), async (req, res) => { if (req.headers['x-api-key'] !== WEBHOOK_SECRET) { return res.status(401).json({ error: 'Unauthorized' }); } const event = req.body; try { // Store raw event await pool.query( `INSERT INTO nudj_events (id, event_category, event_subcategory, organisation_id, community_id, user_id, event_source_id, payload, tags, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (id) DO NOTHING`, [ event.id, event.eventCategory, event.eventSubCategory, event.organisationId, event.communityId, event.userId, event.eventSourceId, JSON.stringify(event.payload), event.tags, event.createdAt ] ); // Update user points for reward events if (event.eventSubCategory === 'reward-distribution') { const { earnedPoints = 0, earnedXp = 0 } = event.payload; await pool.query( `INSERT INTO user_points (user_id, total_points, total_xp) VALUES ($1, $2, $3) ON CONFLICT (user_id) DO UPDATE SET total_points = user_points.total_points + $2, total_xp = user_points.total_xp + $3, last_updated = NOW()`, [event.userId, earnedPoints, earnedXp] ); } res.status(200).json({ success: true }); } catch (error) { console.error('Database error:', error); res.status(500).json({ error: 'Internal error' }); } }); initDatabase().then(() => { app.listen(3000, () => { console.log('Database sync webhook running on port 3000'); }); }); ``` ```javascript theme={null} const express = require('express'); const { MongoClient } = require('mongodb'); const app = express(); const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; const MONGODB_URI = process.env.MONGODB_URI; let db; MongoClient.connect(MONGODB_URI).then(client => { db = client.db('nudj_analytics'); console.log('Connected to MongoDB'); }); app.post('/webhooks/nudj', express.json(), async (req, res) => { if (req.headers['x-api-key'] !== WEBHOOK_SECRET) { return res.status(401).json({ error: 'Unauthorized' }); } const event = req.body; try { // Store event await db.collection('events').insertOne({ ...event, _id: event.id, receivedAt: new Date() }); // Update user statistics if (event.eventSubCategory === 'reward-distribution') { const { earnedPoints = 0, earnedXp = 0 } = event.payload; await db.collection('user_stats').updateOne( { _id: event.userId }, { $inc: { totalPoints: earnedPoints, totalXp: earnedXp, rewardCount: 1 }, $set: { lastActivity: new Date() }, $setOnInsert: { userId: event.userId, createdAt: new Date() } }, { upsert: true } ); } // Track challenge completions if (event.eventSubCategory === 'challenge-completion') { await db.collection('challenge_stats').updateOne( { _id: event.eventSourceId }, { $inc: { completions: 1 }, $addToSet: { completedBy: event.userId } }, { upsert: true } ); } res.status(200).json({ success: true }); } catch (error) { console.error('MongoDB error:', error); res.status(500).json({ error: 'Internal error' }); } }); app.listen(3000, () => { console.log('MongoDB sync webhook running on port 3000'); }); ``` ## Deployment Options ### Serverless Deployment ```javascript theme={null} // api/webhooks/nudj.js export default async function handler(req, res) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); } if (req.headers['x-api-key'] !== process.env.WEBHOOK_SECRET) { return res.status(401).json({ error: 'Unauthorized' }); } const event = req.body; // Process webhook await processWebhook(event); res.status(200).json({ success: true }); } ``` ```javascript theme={null} // handler.js exports.handler = async (event) => { const body = JSON.parse(event.body); const apiKey = event.headers['x-api-key']; if (apiKey !== process.env.WEBHOOK_SECRET) { return { statusCode: 401, body: JSON.stringify({ error: 'Unauthorized' }) }; } // Process webhook await processWebhook(body); return { statusCode: 200, body: JSON.stringify({ success: true }) }; }; ``` # Testing & Configuration Source: https://docs.nudj.cx/developer/webhooks/testing Interactive webhook configuration and testing guide **Interactive API Playground**: Click the cards below to open the API playground where you can directly test and configure your webhooks with your API credentials. ## Interactive Webhook Configuration Configure and test your webhooks using the interactive API playground: View your current webhook settings Configure webhook events and URL ## Get Current Configuration Retrieve your current webhook configuration to see what events you're subscribed to. Click here to get your current webhook configuration using the interactive API playground ### Example Response ```json theme={null} { "configs": [ { "events": [ "challenge-completion", "reward-distribution", "achievement-completion" ], "url": "https://your-webhook-endpoint.com/webhooks/nudj", "httpMethod": "POST", "isEnabled": true, "maxRetries": 3, "headers": { "X-API-Key": "your-secret-key" } } ] } ``` ## Configure Webhooks Update your webhook configuration to subscribe to specific events and set your endpoint URL. Click here to configure your webhooks using the interactive API playground ### Configuration Examples Start with essential events for most integrations: ```json theme={null} { "configs": [ { "events": [ "challenge-completion", "reward-distribution" ], "url": "https://your-app.com/webhooks/nudj", "httpMethod": "POST", "isEnabled": true, "maxRetries": 3, "headers": { "X-API-Key": "your-webhook-secret" } } ] } ``` Subscribe to all available webhook events: ```json theme={null} { "configs": [ { "events": [ "achievement-completion", "auth.email", "challenge-completion", "challenge-started", "organisation.invite-admin-user", "post-comment", "post-like", "reward-claim", "reward-distribution", "reward-redemption", "shop-purchase", "streak-extended" ], "url": "https://your-app.com/webhooks/nudj", "httpMethod": "POST", "isEnabled": true, "maxRetries": 5, "headers": { "X-API-Key": "your-webhook-secret", "X-Custom-Header": "custom-value" } } ] } ``` Configuration for local development and testing: ```json theme={null} { "configs": [ { "events": [ "challenge-completion", "reward-distribution" ], "url": "https://webhook.site/unique-url", "httpMethod": "POST", "isEnabled": true, "maxRetries": 1, "headers": { "X-API-Key": "test-key" } } ] } ``` ## Testing Your Webhook Follow these steps to test your webhook configuration: ### Step 1: Set Up a Test Endpoint 1. Visit [webhook.site](https://webhook.site) 2. Copy your unique URL 3. Use it as your webhook URL in the configuration 4. Monitor incoming requests in real-time 1. Install ngrok: `brew install ngrok` 2. Start your local server: `node server.js` 3. Create tunnel: `ngrok http 3000` 4. Use the HTTPS URL as your webhook endpoint 1. Visit [requestbin.com](https://requestbin.com) 2. Create a new bin 3. Copy the bin URL 4. Use it as your webhook URL ### Step 2: Configure Your Webhook Use the [PUT endpoint](/api-reference/admin/put-webhooks-configs) to set your test webhook URL: ```bash theme={null} curl -X PUT https://{subdomain}.nudj.cx/api/v2/admin/webhooks/configs \ -H "x-api-token: YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "configs": [{ "events": ["challenge-completion"], "url": "https://webhook.site/YOUR-UNIQUE-URL", "httpMethod": "POST", "isEnabled": true, "maxRetries": 1, "headers": { "X-API-Key": "test-secret" } }] }' ``` ### Step 3: Trigger Test Events Perform actions in your Nudj community to trigger webhook events: | Action | Triggered Event | Expected Payload | | - | - | - | | Complete a challenge | `challenge-completion` | Contains challengeId, userId | | Earn rewards | `reward-distribution` | Contains points, XP, rewards | | Unlock achievement | `achievement-completion` | Contains achievement details | | Purchase from shop | `shop-purchase` | Contains purchase details | | Like a post | `post-like` | Contains postId | ### Step 4: Verify Delivery Check your test endpoint to confirm webhook delivery: **What to Look For:** * HTTP method matches configuration (POST/PUT/GET) * Custom headers are present * Event payload structure is correct * Response status is 2xx ## Testing Checklist Use this checklist to ensure your webhook integration is working correctly: Set up webhook.site, ngrok, or RequestBin endpoint Use [PUT endpoint](/api-reference/admin/put-webhooks-configs) to set your webhook URL and events Use [GET endpoint](/api-reference/admin/get-webhooks-configs) to confirm settings are saved Complete a challenge or perform another action Verify webhook received at test endpoint Confirm event structure matches documentation Return non-2xx status to test retry logic Update URL to production endpoint ## Common Test Scenarios ### Testing Retry Logic To test the retry mechanism: 1. Configure webhook with `maxRetries: 3` 2. Have your endpoint return `500` status 3. Observe 3 delivery attempts with 5-second delays 4. After 3 failures, webhook marked as failed ### Testing Authentication Verify your authentication headers: ```javascript theme={null} app.post('/webhooks/test', (req, res) => { const apiKey = req.headers['x-api-key']; if (apiKey !== 'expected-secret') { console.log('Auth failed:', apiKey); return res.status(401).json({ error: 'Unauthorized' }); } console.log('Auth successful'); res.status(200).json({ success: true }); }); ``` ### Testing Idempotency Ensure duplicate events are handled: ```javascript theme={null} const processedEvents = new Set(); app.post('/webhooks/test', (req, res) => { const eventId = req.body.id; if (processedEvents.has(eventId)) { console.log('Duplicate event:', eventId); return res.status(200).json({ duplicate: true }); } processedEvents.add(eventId); console.log('New event:', eventId); res.status(200).json({ success: true }); }); ``` ## Monitoring Webhook Health ### Key Metrics to Track * **Delivery Success Rate**: Should be > 99% * **Average Response Time**: Should be \< 1 second * **Retry Rate**: Should be \< 1% * **Failed Deliveries**: Should be investigated immediately ### Health Check Endpoint Implement a health check for your webhook: ```javascript theme={null} app.get('/webhooks/health', (req, res) => { res.status(200).json({ status: 'healthy', uptime: process.uptime(), memory: process.memoryUsage(), lastWebhook: lastWebhookTime, totalProcessed: webhookCount }); }); ``` ## Troubleshooting Test Issues * Verify webhook is enabled (`isEnabled: true`) * Check URL is publicly accessible (not localhost) * Confirm events are in the subscription list * Ensure organization has active events * Verify custom headers match exactly * Check for case sensitivity in header names * Ensure HTTPS is used (not HTTP) * Compare received payload with documentation * Check JSON parsing for large payloads * Verify character encoding (UTF-8) * Check for timeout issues (> 5 seconds) * Monitor server resource usage * Review network connectivity * Check for rate limiting # Troubleshooting Guide Source: https://docs.nudj.cx/developer/webhooks/troubleshooting Common webhook issues, debugging techniques, and solutions for reliable webhook processing ## Common Issues and Solutions ### Webhook Not Being Received Verify your webhook is properly configured and enabled: ```bash theme={null} curl -X GET "https://{subdomain}.nudj.cx/api/v2/admin/webhooks/configs" \ -H "x-api-token: YOUR_ADMIN_API_TOKEN" ``` **Common issues:** * `isEnabled: false` - Webhook is disabled * Empty `events` array - No events configured * Invalid URL format - Must be valid HTTPS URL * Missing or incorrect headers Test if your webhook endpoint is reachable: ```bash theme={null} # Test basic connectivity curl -X POST "https://your-app.com/webhooks/nudj" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your-webhook-secret" \ -d '{"test": "connectivity"}' ``` **Requirements:** * Must use HTTPS (not HTTP) * Must respond within 10 seconds * Must return HTTP 2xx for success * No redirects (301/302) allowed Ensure the events you're expecting are actually occurring: ```javascript theme={null} // Log all webhook configurations to verify event subscriptions const configs = await fetch(`https://${subdomain}.nudj.cx/api/v2/admin/webhooks/configs`, { headers: { 'x-api-token': 'YOUR_TOKEN' } }).then(r => r.json()); console.log('Subscribed events:', configs[0]?.events); ``` **Verify:** * Events are occurring in your community * Event names match exactly (case-sensitive) * User actions are completing successfully ### Authentication Failures Nudj sends custom headers that your endpoint must verify: ```javascript theme={null} // Correct way to verify webhook authenticity function verifyWebhookSignature(req) { const expectedAuth = `Bearer ${process.env.WEBHOOK_SECRET}`; const receivedAuth = req.get('Authorization'); const webhookSource = req.get('X-Webhook-Source'); if (receivedAuth !== expectedAuth) { console.error('Auth header mismatch:', { expected: expectedAuth, received: receivedAuth }); return false; } if (webhookSource !== 'nudj-platform') { console.error('Invalid webhook source:', webhookSource); return false; } return true; } ``` **Common mistakes:** * Case sensitivity in header names * Missing `Bearer ` prefix * Wrong environment variable * Headers not configured in webhook settings Ensure your secrets are properly configured: ```javascript theme={null} // Debug environment variables console.log('Environment check:', { hasWebhookSecret: !!process.env.WEBHOOK_SECRET, secretLength: process.env.WEBHOOK_SECRET?.length, hasApiToken: !!process.env.NUDJ_ADMIN_API_TOKEN }); ``` **Best practices:** * Use different secrets for development/production * Never commit secrets to version control * Use a secure secret generator * Rotate secrets regularly ### Processing Errors Webhooks must respond within 10 seconds: ```javascript theme={null} // Implement async processing for heavy workloads app.post('/webhooks/nudj', async (req, res) => { try { // Respond immediately res.status(200).send('OK'); // Process asynchronously setImmediate(async () => { try { await processWebhookEvent(req.body); } catch (error) { console.error('Async webhook processing error:', error); } }); } catch (error) { console.error('Webhook error:', error); res.status(500).send('Error'); } }); ``` Optimize webhook processing for high throughput: ```javascript theme={null} // Use connection pooling const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 20 }); // Implement rate limiting const rateLimit = require('express-rate-limit'); const webhookLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 1000, // limit each IP to 1000 requests per windowMs message: 'Too many webhook requests' }); app.use('/webhooks', webhookLimiter); ``` Handle malformed webhook payloads gracefully: ```javascript theme={null} app.use('/webhooks', express.json({ limit: '10mb', verify: (req, res, buf, encoding) => { try { JSON.parse(buf); } catch (error) { console.error('Invalid JSON in webhook:', error); throw new Error('Invalid JSON payload'); } } })); ``` ## Debugging Techniques ### Enable Detailed Logging ```javascript Comprehensive Logging theme={null} const winston = require('winston'); const logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: winston.format.combine( winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json() ), transports: [ new winston.transports.Console(), new winston.transports.File({ filename: 'webhook-debug.log' }) ] }); app.post('/webhooks/nudj', async (req, res) => { const webhookId = req.body?.id || 'unknown'; logger.info('Webhook received', { webhookId, headers: req.headers, body: req.body, timestamp: new Date().toISOString() }); try { // Verify signature if (!verifyWebhookSignature(req)) { logger.warn('Webhook signature verification failed', { webhookId, receivedAuth: req.get('Authorization'), receivedSource: req.get('X-Webhook-Source') }); return res.status(401).send('Unauthorized'); } logger.info('Webhook signature verified', { webhookId }); // Process event await processWebhookEvent(req.body); logger.info('Webhook processed successfully', { webhookId }); res.status(200).send('OK'); } catch (error) { logger.error('Webhook processing failed', { webhookId, error: error.message, stack: error.stack }); res.status(500).send('Internal Server Error'); } }); ``` ### Request Inspection Tools ```javascript Request Inspector Middleware theme={null} function requestInspector(req, res, next) { const originalSend = res.send; const startTime = Date.now(); // Log incoming request console.log('=== INCOMING WEBHOOK ==='); console.log('Method:', req.method); console.log('URL:', req.url); console.log('Headers:', JSON.stringify(req.headers, null, 2)); console.log('Body:', JSON.stringify(req.body, null, 2)); console.log('Timestamp:', new Date().toISOString()); // Override res.send to log response res.send = function(data) { const duration = Date.now() - startTime; console.log('=== OUTGOING RESPONSE ==='); console.log('Status:', res.statusCode); console.log('Duration:', duration + 'ms'); console.log('Response:', data); console.log('========================'); return originalSend.call(this, data); }; next(); } // Use only in development if (process.env.NODE_ENV === 'development') { app.use('/webhooks', requestInspector); } ``` ### Webhook Testing Tools ```javascript Local Testing Setup theme={null} // webhook-tester.js const express = require('express'); const app = express(); app.use(express.json()); app.post('/webhooks/nudj', (req, res) => { console.log('=== WEBHOOK TEST ==='); console.log('Headers:', req.headers); console.log('Body:', JSON.stringify(req.body, null, 2)); console.log('=================='); res.status(200).json({ message: 'Webhook received successfully', timestamp: new Date().toISOString(), webhookId: req.body?.id }); }); const port = process.env.PORT || 3000; app.listen(port, () => { console.log(`Webhook test server running on port ${port}`); console.log(`Use ngrok to expose: ngrok http ${port}`); }); ``` ```bash Testing Script theme={null} #!/bin/bash # test-webhook.sh WEBHOOK_URL="https://your-app.com/webhooks/nudj" SECRET="your-webhook-secret" # Test payload payload='{ "id": "test-'$(date +%s)'", "eventCategory": "challenge", "eventSubCategory": "challenge-completion", "organisationId": "test-org-id", "communityId": "test-community-id", "userId": "test-user-id", "eventSourceId": "test-challenge-id", "payload": { "challengeName": "Test Challenge", "pointsEarned": 100 }, "createdAt": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'" }' echo "Testing webhook endpoint..." echo "URL: $WEBHOOK_URL" echo "Payload: $payload" response=$(curl -s -w "\n%{http_code}" -X POST "$WEBHOOK_URL" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $SECRET" \ -H "X-Webhook-Source: nudj-platform" \ -d "$payload") echo "Response: $response" ``` ## Error Monitoring and Alerting ### Webhook Health Checks ```javascript Health Monitoring theme={null} class WebhookHealthMonitor { constructor() { this.stats = { totalReceived: 0, totalProcessed: 0, totalFailed: 0, lastProcessed: null, avgProcessingTime: 0 }; this.processingTimes = []; this.maxSamples = 100; } recordWebhook(processingTime, success) { this.stats.totalReceived++; if (success) { this.stats.totalProcessed++; this.stats.lastProcessed = new Date(); // Track processing time this.processingTimes.push(processingTime); if (this.processingTimes.length > this.maxSamples) { this.processingTimes.shift(); } this.stats.avgProcessingTime = this.processingTimes.reduce((a, b) => a + b, 0) / this.processingTimes.length; } else { this.stats.totalFailed++; } } getHealthStatus() { const successRate = (this.stats.totalProcessed / this.stats.totalReceived) * 100; const isHealthy = successRate > 95 && this.stats.avgProcessingTime < 1000; return { healthy: isHealthy, stats: this.stats, successRate: successRate.toFixed(2) + '%', alerts: this.generateAlerts() }; } generateAlerts() { const alerts = []; const successRate = (this.stats.totalProcessed / this.stats.totalReceived) * 100; if (successRate < 90) { alerts.push({ level: 'critical', message: `Low success rate: ${successRate.toFixed(2)}%` }); } if (this.stats.avgProcessingTime > 5000) { alerts.push({ level: 'warning', message: `High avg processing time: ${this.stats.avgProcessingTime}ms` }); } if (this.stats.lastProcessed && Date.now() - this.stats.lastProcessed > 3600000) { alerts.push({ level: 'warning', message: 'No webhooks processed in last hour' }); } return alerts; } } const healthMonitor = new WebhookHealthMonitor(); // Health check endpoint app.get('/webhooks/health', (req, res) => { const health = healthMonitor.getHealthStatus(); res.status(health.healthy ? 200 : 503).json(health); }); ``` ### Error Notifications ```javascript Alert System theme={null} class WebhookAlertSystem { constructor() { this.errorThresholds = { failureRate: 10, // 10% failure rate triggers alert consecutiveFailures: 5, avgProcessingTime: 5000 // 5 seconds }; this.consecutiveFailures = 0; this.recentErrors = []; } async handleError(error, webhookData) { this.consecutiveFailures++; this.recentErrors.push({ timestamp: new Date(), error: error.message, webhookId: webhookData.id, eventType: webhookData.eventSubCategory }); // Keep only last 50 errors if (this.recentErrors.length > 50) { this.recentErrors = this.recentErrors.slice(-50); } // Check if we need to send alerts await this.checkAlertThresholds(); } handleSuccess() { this.consecutiveFailures = 0; } async checkAlertThresholds() { if (this.consecutiveFailures >= this.errorThresholds.consecutiveFailures) { await this.sendAlert('critical', `${this.consecutiveFailures} consecutive webhook failures`); } // Check failure rate in last 10 minutes const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000); const recentErrors = this.recentErrors.filter(e => e.timestamp > tenMinutesAgo); if (recentErrors.length >= this.errorThresholds.failureRate) { await this.sendAlert('warning', `High error rate: ${recentErrors.length} errors in last 10 minutes`); } } async sendAlert(level, message) { const alertData = { level, message, service: 'webhook-handler', timestamp: new Date().toISOString(), recentErrors: this.recentErrors.slice(-5) // Last 5 errors }; // Send to multiple channels await Promise.all([ this.sendSlackAlert(alertData), this.sendEmailAlert(alertData), this.logAlert(alertData) ]); } async sendSlackAlert(alertData) { // Implement Slack webhook notification console.log('Slack alert:', alertData); } async sendEmailAlert(alertData) { // Implement email notification console.log('Email alert:', alertData); } async logAlert(alertData) { console.error('WEBHOOK ALERT:', alertData); } } const alertSystem = new WebhookAlertSystem(); ``` ## Performance Optimization ### Async Processing ```javascript Queue-Based Processing theme={null} const Queue = require('bull'); const webhookQueue = new Queue('webhook processing'); // Add webhook to queue immediately app.post('/webhooks/nudj', async (req, res) => { try { // Verify signature quickly if (!verifyWebhookSignature(req)) { return res.status(401).send('Unauthorized'); } // Add to queue for async processing await webhookQueue.add('process', req.body, { attempts: 3, backoff: 'exponential', delay: 0 }); // Respond immediately res.status(200).send('OK'); } catch (error) { console.error('Error queuing webhook:', error); res.status(500).send('Error'); } }); // Process webhooks from queue webhookQueue.process('process', async (job) => { const webhookData = job.data; try { await processWebhookEvent(webhookData); console.log(`Processed webhook ${webhookData.id}`); } catch (error) { console.error(`Failed to process webhook ${webhookData.id}:`, error); throw error; // Will trigger retry } }); ``` ### Caching Strategies ```javascript Smart Caching theme={null} const NodeCache = require('node-cache'); const cache = new NodeCache({ stdTTL: 300 }); // 5 minute TTL async function processWebhookEvent(event) { const cacheKey = `user_${event.userId}`; // Try to get user from cache let user = cache.get(cacheKey); if (!user) { user = await getUserFromDatabase(event.userId); cache.set(cacheKey, user); } // Process with cached user data await processEventWithUser(event, user); } // Cache webhook configurations async function getWebhookConfig(organisationId) { const cacheKey = `webhook_config_${organisationId}`; let config = cache.get(cacheKey); if (!config) { config = await fetchWebhookConfig(organisationId); cache.set(cacheKey, config, 600); // Cache for 10 minutes } return config; } ``` ## Testing and Validation ### Integration Testing ```javascript Integration Tests theme={null} const request = require('supertest'); const app = require('../webhook-server'); describe('Webhook Integration Tests', () => { it('should process challenge completion webhook', async () => { const payload = { id: 'test-webhook-123', eventCategory: 'challenge', eventSubCategory: 'challenge-completion', userId: 'test-user-123', payload: { challengeName: 'Test Challenge', pointsEarned: 100 } }; const response = await request(app) .post('/webhooks/nudj') .set('Authorization', `Bearer ${process.env.WEBHOOK_SECRET}`) .set('X-Webhook-Source', 'nudj-platform') .send(payload) .expect(200); // Verify processing occurred const user = await getUserFromDatabase('test-user-123'); expect(user.totalPointsEarned).toBeGreaterThan(0); }); it('should reject invalid signatures', async () => { const payload = { id: 'test-webhook-invalid' }; await request(app) .post('/webhooks/nudj') .set('Authorization', 'Bearer invalid-secret') .send(payload) .expect(401); }); it('should handle duplicate webhooks', async () => { const payload = { id: 'duplicate-test-webhook', eventSubCategory: 'test-event' }; // Send same webhook twice await request(app) .post('/webhooks/nudj') .set('Authorization', `Bearer ${process.env.WEBHOOK_SECRET}`) .set('X-Webhook-Source', 'nudj-platform') .send(payload) .expect(200); await request(app) .post('/webhooks/nudj') .set('Authorization', `Bearer ${process.env.WEBHOOK_SECRET}`) .set('X-Webhook-Source', 'nudj-platform') .send(payload) .expect(200); // Verify processing only happened once // Add your verification logic here }); }); ``` ## Common Solutions ### Network and Connectivity Ensure your firewall allows incoming HTTPS traffic: ```bash theme={null} # Check if port 443 is open sudo ufw status sudo ufw allow 443 # For development (port 3000) sudo ufw allow 3000 ``` Configure your load balancer for webhook traffic: * Enable session affinity if needed * Set appropriate timeout values (>10 seconds) * Configure health checks * Ensure SSL termination is properly configured Verify your domain configuration: ```bash theme={null} # Check DNS resolution nslookup your-webhook-domain.com # Test SSL certificate openssl s_client -connect your-webhook-domain.com:443 # Verify certificate chain curl -I https://your-webhook-domain.com/webhooks/nudj ``` ## Getting Help Start by examining your application logs and webhook processing logs for error patterns. Use ngrok or similar tools to test webhooks against your local development environment. Double-check your webhook configuration using the Admin API endpoints. Reach out to [support@nudj.cx](mailto:support@nudj.cx) with specific error messages and webhook IDs. When contacting support, please include: * Webhook configuration (without secrets) * Specific error messages and stack traces * Webhook IDs that failed * Timeline of when issues started * Your endpoint URL (for connectivity testing) ## Debugging Checklist * [ ] Webhook is enabled (`isEnabled: true`) * [ ] Events array contains expected event types * [ ] URL is valid and reachable * [ ] HTTP method is appropriate * [ ] Headers are correctly configured * [ ] Endpoint responds within 10 seconds * [ ] Returns HTTP 2xx for successful processing * [ ] Properly verifies webhook signatures * [ ] Handles JSON parsing errors * [ ] Implements idempotency * [ ] Endpoint is accessible from external networks * [ ] SSL certificate is valid * [ ] Firewall allows HTTPS traffic * [ ] DNS resolves correctly * [ ] Comprehensive logging is enabled * [ ] Error monitoring is configured * [ ] Health checks are implemented * [ ] Metrics are being tracked Still having issues? Check our [Implementation Guide](/developer/webhooks/implementation) or review the [Event Catalog](/developer/webhooks/event-catalog) to ensure you're handling webhooks correctly. # API Link User Token Auth Source: https://docs.nudj.cx/enterprise/api-link-user-authentication Authenticate users directly into Nudj Platform using JWT tokens that encode user metadata For the current widget integration, use an HS256 identity JWT with the signing key ID in the `kid` header, the authenticated external user ID in `sub`, and a five-minute lifetime. Merchant identity tokens have a ten-minute maximum. The [worked game integration](/developer/game-integration) shows inline embedding, native member sessions and signed event claims, including the distinction between each token. Never log token values or token-bearing URLs. This guide explains how to authenticate users directly into the Nudj Platform using JWT tokens that encode user metadata. This method is suitable for scenarios where users are already authenticated in your platform and you want to pass them into Nudj seamlessly. ## Quick Start ✅ Ensure users are already authenticated in your platform and you want to pass them into Nudj without showing a login screen. Retrieve your credentials from [Developer Settings](https://admin.nudj.cx/admin/settings/organisation?tab=developer): * Client ID * Client Secret * API Token (for alternative method) Generate a signed JWT containing the user's details (userId, email, locale, etc.) using your clientSecret. Create the authentication URL: ``` https://${yourDomain}/api/link?userToken=${userToken}&clientId=${clientId} ``` Share the link with the user or redirect them to it for instant Nudj login. ## When to Use This Method * Users are already authenticated in your platform * You want seamless session continuation in Nudj * No login screen should be shown * Single sign-on (SSO) experience required * Users arrive directly at Nudj without authentication * Users need to create new accounts * OAuth integration is more appropriate If users arrive directly at Nudj without being authenticated, use [OAuth Integration Setup](/enterprise/oauth-authentication) instead. ## Detailed Implementation Guide ## 1. Retrieve Your Credentials Navigate to **Admin Panel → Settings → Organization → Developer Settings** tab and copy your: Your unique client identifier Your secret key for signing tokens (keep this secure!) API token for alternative authentication method (optional) Never expose your Client Secret in client-side code or public repositories. Always generate tokens on your backend server. Note: Nudj stores organization-level credentials. In your application, load secrets securely (e.g., environment variables or a secret manager), never from client code. ## 2. Generate the User Token ```javascript JavaScript theme={null} import { sign } from "jsonwebtoken"; import { randomUUID } from "crypto"; const userData = { userId: "abc123", // Unique identifier for the user email: "abc123@example.com", // User's email locale: "en", // Optional locale, defaults to "en" if not provided username: "John Doe", // Optional display name points: 100, // Optional initial points (only for new users) }; function signUserToken(input) { const clientSecret = process.env.NUDJ_CLIENT_SECRET; if (!clientSecret) { throw new Error("NUDJ_CLIENT_SECRET is not set"); } const now = Math.floor(Date.now() / 1000); const claims = { ...input, iss: "your-app", // Issuer aud: "nudj", // Audience iat: now, // Issued at nbf: now, // Not before jti: randomUUID(), // JWT ID for tracking }; return sign(claims, clientSecret, { algorithm: "HS256", expiresIn: "5m" }); } const userToken = signUserToken(userData); // Never log tokens in production - store or use securely ``` ```python Python theme={null} import jwt from datetime import datetime, timedelta import os import uuid user_data = { "userId": "abc123", # Unique identifier for the user "email": "abc123@example.com", # User's email "locale": "en", # Optional locale, defaults to "en" "username": "John Doe", # Optional display name "points": 100, # Optional initial points } def sign_user_token(payload): client_secret = os.environ.get('NUDJ_CLIENT_SECRET') if not client_secret: raise ValueError("NUDJ_CLIENT_SECRET is not set") now = datetime.utcnow() claims = { **payload, "iss": "your-app", # Issuer "aud": "nudj", # Audience "iat": now, # Issued at "nbf": now, # Not before "exp": now + timedelta(minutes=5), # Expiry "jti": str(uuid.uuid4()), # JWT ID for tracking } return jwt.encode(claims, client_secret, algorithm='HS256') user_token = sign_user_token(user_data) # Never log tokens in production - store or use securely ``` ```typescript TypeScript theme={null} import { sign } from "jsonwebtoken"; import { randomUUID } from "node:crypto"; interface UserData { userId: string; email?: string; locale?: string; username?: string; anonymousAccountId?: string; points?: number; // Initial points for new users } function signUserToken(input: UserData): string { const clientSecret = process.env.NUDJ_CLIENT_SECRET; if (!clientSecret) { throw new Error("NUDJ_CLIENT_SECRET is not set"); } const now = Math.floor(Date.now() / 1000); const claims = { ...input, iss: "your-app", // Issuer aud: "nudj", // Audience iat: now, // Issued at nbf: now, // Not before jti: randomUUID(), // JWT ID for tracking }; return sign(claims, clientSecret, { algorithm: "HS256", expiresIn: "5m", keyid: process.env.NUDJ_CLIENT_KID, // Optional key ID }); } const userData: UserData = { userId: "abc123", email: "abc123@example.com", locale: "en", points: 100, // Optional initial points }; const userToken = signUserToken(userData); // Never log tokens in production - store or use securely ``` ## 3. Build the Auto-Login URL You can use either the **User Token method** (recommended) or the **API Token method**: ```javascript theme={null} const domain = "yourcompany.nudj.cx"; // Your Nudj domain const clientId = process.env.NUDJ_CLIENT_ID; // Your Client ID // Use URL constructor for proper encoding const url = new URL(`https://${domain}/api/link`); url.searchParams.set('userToken', userToken); url.searchParams.set('clientId', clientId); // Optional: Add callback path (must be a relative path starting with /) url.searchParams.set('callbackPath', '/dashboard'); const linkUrl = url.toString(); // Server-side redirect (REQUIRED for security) res.redirect(linkUrl); ``` ```javascript theme={null} const domain = "yourcompany.nudj.cx"; // Your Nudj domain const apiToken = process.env.NUDJ_API_TOKEN; // Your API Token // Build URL with individual parameters const params = new URLSearchParams({ apiToken: apiToken, userId: "abc123", email: "user@example.com", locale: "en", username: "John Doe" }); const linkUrl = `https://${domain}/api/link?${params.toString()}`; // Server-side redirect (REQUIRED for security) res.redirect(linkUrl); ``` **Critical Security Warning**: Never put JWTs or API tokens in frontend redirects using `window.location.href` as this exposes sensitive credentials in browser history, logs, and referrer headers. Always use server-side redirects (`res.redirect()`) to transmit signed tokens. If client-side navigation is absolutely required, implement a secure token exchange flow using short-lived authorization codes instead of exposing JWTs directly. **Security Note**: The `callbackPath` parameter must be a relative path starting with `/` and will be validated against an allowlist on the server to prevent open-redirect vulnerabilities. Full URLs or paths missing the leading slash will be rejected. ## 4. User Property Requirements Unique identifier for the user. This is stored as `externalId` in Nudj and used for account identification. User's email address. Must be unique per user. If omitted, Nudj generates a placeholder email in the format `${userId}@${organisationId}.com` and the user can only log in via direct links (no magic link/self-login). Language/region code (e.g., `en`, `fr`, `es`). Defaults to `en` if not provided. Display name shown in Nudj UI. Defaults to "anonymous" until updated by the user. Special identifier provided by Nudj to merge an anonymous session with the authenticated user account. Used for preserving user progress before authentication. This ID is typically available in the session or URL parameters when anonymous login features are enabled. Initial points to award the user upon account creation. Only applied for new users, not updates. Creates a points transaction record automatically. ## How It Works ## Account Creation & Management When a user doesn't exist in Nudj: 1. Nudj receives the signed JWT token 2. Validates the token signature using your Client Secret 3. Creates a new account with provided details 4. Awards initial points if specified (creates transaction record) 5. Automatically signs the user in 6. User lands in Nudj fully authenticated When a user already exists: 1. Nudj identifies the user by `userId` (externalId) 2. Updates any changed properties (locale, username) 3. Signs the user into their existing account 4. Preserves all user progress and data When merging anonymous sessions: 1. User starts as anonymous visitor (requires `AnonymousLogin` feature flag) 2. Nudj provides `anonymousAccountId` in session/URL 3. Include this ID when authenticating 4. Nudj merges anonymous progress with authenticated account 5. User retains all activity from before login Anonymous account merging requires the `AnonymousLogin` and `anonymous-account-conversion` feature flags to be enabled for your organization. ## Example Implementation Flow User logs into your platform using your existing authentication system. Your backend generates a signed JWT with the user's information. Your system builds the Nudj authentication URL with the token and client ID. User is redirected to the Nudj URL either automatically or via a button/link. User lands in Nudj fully signed in with no login screen shown. ## Environment Configuration The API Link authentication works across multiple Nudj applications: ```javascript theme={null} const config = { domain: "yourcompany.nudj.cx", endpoint: "/api/link", protocol: "https" }; ``` ```javascript theme={null} const config = { domain: "yourcompany-staging.nudj.cx", endpoint: "/api/link", protocol: "https" }; ``` ```javascript theme={null} const config = { domain: "localhost:3000", // or localhost:4000 for admin endpoint: "/api/link", protocol: "http" }; ``` The `/api/link` endpoint is available in multiple Nudj applications: * **user** app (port 3000) * **admin** app (port 4000) * **creator-frontend** app * **api** service All applications share the same authentication logic and user database. ## Complete Integration Example Here's a full example of implementing API Link authentication in an Express.js application: ```javascript theme={null} import express from 'express'; import { sign } from 'jsonwebtoken'; const app = express(); // Middleware to check if user is authenticated in your system function requireAuth(req, res, next) { if (!req.session.user) { return res.redirect('/login'); } next(); } // Route to redirect authenticated users to Nudj app.get('/nudj-redirect', requireAuth, (req, res) => { const { user } = req.session; // Prepare user data for Nudj const nudjUserData = { userId: user.id, email: user.email, username: user.displayName, locale: user.preferredLanguage || 'en' }; // Sign the token const userToken = sign( nudjUserData, process.env.NUDJ_CLIENT_SECRET, { expiresIn: '5m' } ); // Build the Nudj URL const nudjDomain = process.env.NUDJ_DOMAIN; // e.g., "yourcompany.nudj.cx" const clientId = process.env.NUDJ_CLIENT_ID; const nudjUrl = `https://${nudjDomain}/api/link?userToken=${userToken}&clientId=${clientId}`; // Redirect user to Nudj res.redirect(nudjUrl); }); app.listen(3000, () => { console.log('Server running on port 3000'); }); ``` ## Error Responses The `/api/link` endpoint returns specific HTTP status codes for different error conditions: | Status Code | Error | Description | | - | - | - | | `401` | Unauthorized | Missing or invalid token/credentials | | `403` | Forbidden | Valid token but insufficient permissions | | `404` | Not Found | Domain or organization not found | | `422` | Unprocessable Entity | Invalid user data or parameters | | `500` | Internal Server Error | Server-side processing error | Example error response: ```json theme={null} { "error": "Invalid token", "message": "The provided user token could not be validated", "code": "INVALID_TOKEN" } ``` ## Security Best Practices Always follow these security guidelines when implementing API Link authentication: * Generate tokens only on your backend server * Never expose Client Secret in client-side code * Use environment variables for sensitive credentials * Set appropriate token expiration times (5 minutes recommended, 10 minutes maximum) * Rotate Client Secrets periodically * Note: Organizations can have multiple Client Secrets configured * Validate user data before token generation * Ensure userId is unique and consistent * Sanitize email addresses and usernames * Handle missing optional fields gracefully * Verify email format if provided * Implement proper error handling for token generation failures * Provide fallback authentication methods * Log authentication attempts for security monitoring * Handle expired tokens gracefully * Implement retry logic with exponential backoff * Configure CORS to allow your domains * Implement CSRF protection for session management * Use HTTPS for all authentication requests * Set appropriate security headers ## Troubleshooting **Problem**: JWT signing throws an error **Solutions**: * Verify Client Secret is correctly set in environment variables * Check that the jsonwebtoken library is properly installed * Ensure user data object has all required fields * Validate that expiration time is properly formatted **Problem**: User is redirected but sees login screen **Solutions**: * Verify the token is properly URL-encoded in the link * Check that clientId matches your Nudj configuration * Ensure token hasn't expired (check expiration time) * Validate domain matches your Nudj instance **Problem**: Changes to user properties don't reflect in Nudj **Solutions**: * Ensure userId remains consistent (it's the primary identifier) * Check that updated fields are included in the token * Verify token signature is valid * Clear browser cache and try again **Problem**: User loses progress after authentication **Solutions**: * Include the anonymousAccountId provided by Nudj * Ensure the ID is passed correctly in the token * Check timing - merge must happen during authentication * Contact Nudj support if issue persists ## API Reference For detailed API documentation and additional authentication methods, see: Complete guide to all Nudj authentication methods Set up OAuth providers for direct login Get API tokens and pick an auth method Full Integration API reference ## Support If you encounter any issues or need assistance with implementation: * **Technical Support**: Contact your Nudj account manager * **Documentation**: Visit [docs.nudj.cx](https://docs.nudj.cx) * **API Status**: Check [status.nudj.cx](https://status.nudj.cx) * **Community**: Join the Nudj Developer Community # Cookie Consent Source: https://docs.nudj.cx/enterprise/cookie-consent OneTrust and Osano cookie consent integrations for analytics providers, with per-organisation configuration. Nudj integrates with [OneTrust](https://www.onetrust.com/products/cookie-consent/) and [Osano](https://www.osano.com/) to gate analytics providers behind user consent. Both are supported independently — configure either one, both, or neither per organisation. OneTrust and Osano live on your organisation's marketing property and are expected to already be present on any surface where Nudj renders. Nudj reads the active CMP's consent state at runtime and conditions analytics loading on it. ## What gets gated Consent state gates every analytics provider Nudj ships to the browser: * **PostHog** — product analytics + session replay. * **Google Analytics** — page views and events. * **Meta Pixel** — advertising events. * **BetterStack session replay** — frontend session replay telemetry. Server-side logging is unaffected — the log pipeline (BetterStack observability) does not route through OneTrust or Osano. ## How consent flows OneTrust exposes a global object on the page enumerating the consent categories the user has granted (e.g. `C0001`, `C0002`, `C0003`, `C0004`). The Nudj user app checks the consent state before initialising each analytics provider. A provider gated on a category the user hasn't consented to never loads at all. OneTrust fires change events when the user updates their preferences. Nudj listens for these and updates provider loading state accordingly. ## Per-organisation configuration Cookie consent is organisation-level. An organisation can: * **Enable or disable OneTrust integration entirely.** Organisations without a OneTrust licence can opt out; without the integration active, Nudj falls back to the analytics configuration set elsewhere in the organisation. * **Map categories to providers.** Each analytics provider is keyed to a consent category; the mapping is part of the organisation's analytics configuration. ## Propagation into embedded surfaces The OneTrust integration also applies when the Nudj user app is embedded: * **Shopify storefront widgets** — the merchant's OneTrust banner (on their storefront) gates the Nudj widgets' analytics loading. * **Universal embed widget** — the embedding page's OneTrust state propagates to the embedded widget. If your embedding page doesn't run OneTrust, the Nudj widget falls back to the organisation-level analytics configuration. In regulated geographies this typically means analytics are disabled by default. ## Verifying consent gating From a test page: 1. Open the browser devtools Network panel. 2. Load the page with OneTrust banner dismissed "reject all". 3. Confirm no requests to PostHog / GA / Meta Pixel fire. 4. Reload with "accept all". 5. Confirm the providers' requests now fire. ## Osano [Osano](https://www.osano.com/) is a cookie-consent CMP that Nudj supports alongside OneTrust. When enabled, the Osano banner loads on your Nudj surfaces and its consent state gates the same analytics providers listed above. OneTrust and Osano are configured independently. Enable either, both, or neither. If both are configured, both banners will load — in practice most organisations pick one CMP. ### Enable Osano In your Osano dashboard, grab the embed script URL for the property you're integrating. It has the form `https://cmp.osano.com/{customer-id}/{config-id}/osano.js`. In Nudj, open **Organisation Settings → Third Party → Osano** and paste the URL into the **Script URL** field. Save. Load a Nudj-embedded surface. The Osano banner should appear and its consent state should gate analytics loading exactly as described in [Verifying consent gating](#verifying-consent-gating) above. Required consent categories for gating the community rules modal are not yet exposed via the admin UI — this is an engineering hook that can be wired up per-community in a future release. Contact your Nudj representative if you need this today. ## Related pages How the Nudj experience renders inside your surface. The primary non-Shopify embed path. > Last reviewed: 2026-04 · Related PRs: #973 # Configuring Data Hydration Source: https://docs.nudj.cx/enterprise/customer-data-configuration Step-by-step guide to enabling customer data hydration on question actions in the Nudj admin panel Customer data hydration is configured per question action within a challenge. When enabled, every time a user answers that question, their response is captured as structured customer data. Data hydration appears as a collapsible section on question action editors. It is only available for question-type actions (multiple choice, open-ended, rating, etc.). ## Prerequisites Before configuring data hydration, ensure you have: * **Admin access** to the Nudj control room with Manager role or above * **An existing challenge** with at least one question-type action (multiple choice, open-ended, rating, date selection, etc.) * **Shopify connection** (optional) — if you want to sync data to Shopify, your community must have a linked Shopify store with the `write_customers` scope ## Enabling Data Hydration Navigate to **Engagement > Challenges** in the admin panel and open the challenge containing your question action. Find the question action you want to hydrate and expand its configuration. Scroll to the **Customer Data** section. Toggle **Enable Customer Data Collection** to on. This activates the generic custom data layer — answers will be stored on user profiles immediately. Set up the field configuration: * **Field Key** — A unique identifier for this data field. Auto-suggested from the question text. Must be lowercase alphanumeric with underscores (e.g., `favorite_color`). * **Data Type** — How the value should be stored: Text, Integer, Decimal, Boolean, or Date. * **Display Name** — A human-readable label for the field (e.g., "Favorite Color"). * **Description** — Optional description explaining what this field captures. For questions where users can select multiple options, choose a multi-select mode: * **None** — Single selection only. * **Comma Separated** — All selections joined as text (e.g., `"Red, Blue, Green"`). * **Boolean Expansion** — Each option becomes its own true/false field. Limited to 5 options. For boolean expansion, you can also set a **Choices Override** to control exactly which options appear as boolean fields. Save the challenge. If a platform sync is configured, definition sync jobs are queued automatically in the background. ## Field Key Best Practices The field key is the most important configuration choice because it becomes immutable after the first sync. * Use descriptive, stable names: `shirt_size`, `dietary_preference` * Use snake\_case consistently * Keep keys short but meaningful (max 64 characters) * Reuse the same key across actions if they capture the same data point * Use vague names: `q1`, `answer`, `data` * Include action or challenge IDs in the key * Change your mind after sync — the key locks permanently * Use different keys for the same semantic data point across communities in your organization Multiple actions can share the same field key if they use the same data type. The most recent answer overwrites the previous value. This is useful for re-asking preference questions — the user's latest answer is always the stored value. ## Data Type Selection Choose the data type that best matches your question and how you plan to use the data downstream: | Question Type | Recommended Data Type | Why | | - | - | - | | "What's your favorite color?" | Text | Free-form preference | | "How many pets do you have?" | Integer | Whole number count | | "Rate this product (1-5)" | Decimal | Allows fractional ratings | | "Do you prefer morning delivery?" | Boolean | Yes/no question | | "When is your birthday?" | Date | Calendar date | | "Pick your top 3 interests" | Text + Comma Separated | Multi-select labels | The data type cannot be changed after the first external platform sync. Choose carefully based on your downstream usage, not just the question format. ## Multi-Select Mode Details ### Comma Separated Stores all selected labels as a single comma-delimited text value. Supports up to 50 choices. ```text theme={null} Field: favorite_genres Value: "Action, Comedy, Sci-Fi" ``` **Pros**: Simple, unlimited options, single field to manage. **Cons**: Harder to filter on individual selections in some platforms. ### Boolean Expansion Creates a separate boolean field for each option. Each field is `true` if the user selected that option, `false` otherwise. ```text theme={null} Field: favorite_genres_action → true Field: favorite_genres_comedy → true Field: favorite_genres_scifi → false Field: favorite_genres_drama → false Field: favorite_genres_horror → false ``` **Pros**: Native boolean filtering on each option, works well with Shopify customer segments. **Cons**: Limited to 5 options per action, uses more metafield definition capacity on external platforms. Boolean expansion keys are generated from the parent field key plus the option ID. The expansion keys are managed automatically — you only configure the parent key. ## Immutability Rules Once data has been synced to any external platform (indicated by a `firstSyncedAt` timestamp), certain fields lock: | Field | After First Sync | | - | - | | Field Key | Locked | | Data Type | Locked | | Multi-Select Mode | Locked | | Choices Override | Locked | | Display Name | Editable | | Description | Editable | | Platform-specific settings (e.g., pin to customer detail) | Editable | The admin UI shows a warning banner when fields are locked, and locked inputs are visually disabled. You cannot remove a platform sync configuration after the first sync. This prevents orphaning definitions on the external platform. If you need to stop syncing, disable the data hydration toggle instead. ## Schema Consistency Nudj enforces type consistency per field key within a community: * If action A uses `shirt_size` as **Text**, action B cannot use `shirt_size` as **Integer**. * If action A uses `shirt_size` with **Boolean Expansion**, action B cannot share the `shirt_size` key (boolean expansion keys are exclusive to one action). * Multiple actions can share the same key with the same type and mode — the latest answer wins. Conflicts are detected at save time and reported as validation errors. ## Capacity Limits External platforms may have limits on field definitions: | Platform | Limit | What Counts | | - | - | - | | Shopify | 256 metafield definitions per namespace | Each field key = 1 definition. Boolean expansion: each option = 1 definition. | The admin panel shows a warning when approaching the limit. New definitions that would exceed the limit are rejected at save time. ## Next Steps Configure Shopify-specific sync settings including metafield pinning and customer detail visibility. Review the full architecture and supported action types. # Customer Data Hydration Source: https://docs.nudj.cx/enterprise/customer-data-overview Capture user engagement data from wizard questions and sync it to external platforms like Shopify for segmentation and personalization When users answer questions in your Nudj challenges, those answers represent valuable customer data — preferences, interests, demographics, and intent signals. Customer Data Hydration captures those answers and makes them available both inside Nudj and in your external platforms, enabling powerful customer segmentation without manual data entry. **How it works**: When a user completes a question action with data hydration enabled, Nudj stores the answer as structured custom data on the user's profile, then asynchronously syncs it to any connected external platforms. ## Two-Layer Architecture Customer Data Hydration operates in two layers. The first layer is always active when hydration is enabled. The second layer activates only when a platform sync is configured. **Always active when enabled** Stores resolved answers on the user's community profile as structured key-value data. Available immediately for use within Nudj — segmentation, personalization, and reporting. **Optional, per-platform** Asynchronously syncs the same data to external platforms (e.g., Shopify customer metafields). Non-blocking — the user's participation flow is never delayed by external sync. ## Data Flow A user completes a question action (multiple choice, open-ended, rating, date selection, etc.) within a challenge. Nudj resolves the raw answer into a human-readable value. For choice-based questions, option IDs are mapped to their labels. For typed questions, the value is coerced to the configured data type (text, integer, decimal, boolean, or date). The resolved value is written to the user's community profile under a configurable field key. A schema definition is also tracked to ensure type consistency across actions sharing the same key. If a platform sync is configured (e.g., Shopify), an async job is queued to push the data to the external platform. This happens in the background and does not affect the user's experience. ## Supported Action Types Data hydration works with any question-type action. The system automatically extracts the appropriate value based on the action type: | Action Type | Extracted Value | | - | - | | Multiple Choice | Selected option label(s) | | Select from List | Selected option label(s) | | Select from Images | Selected option label(s) | | Vote | Voted option label | | Open Ended | Free-text answer | | Rate Your Experience | Numeric rating | | Select Value in Range | Selected numeric value | | Select Date | Date in ISO format | | Single Fixed Answer | The fixed answer text | | Secret Access Code | The entered code | ## Data Types Each hydrated field has a configured data type that determines how values are stored and validated: | Type | Description | Example | | - | - | - | | **Text** | Free-form string | `"Blue"`, `"New York"` | | **Integer** | Whole number | `42`, `7` | | **Decimal** | Decimal number | `4.5`, `9.99` | | **Boolean** | True/false | `true`, `false` | | **Date** | ISO date string | `"2025-03-15"` | If type coercion fails (e.g., a user enters "hello" for an integer field), the raw text value is still stored in Nudj's generic custom data layer. Only the external platform sync is skipped for that particular answer. ## Multi-Select Handling For question actions where users can select multiple options, two storage modes are available: All selected labels are joined into a single text value. **Field key**: `favorite_colors` **Stored value**: `"Red, Blue, Green"` Best for: simple reporting, unlimited options, text-based platforms. Each option becomes its own boolean field. Limited to 5 options per action. **Field keys**: `favorite_colors_opt1`, `favorite_colors_opt2`, `favorite_colors_opt3` **Stored values**: `true`, `false`, `true` Best for: platforms that support boolean filters (e.g., Shopify customer segments), when you need per-option filtering. ## Platform Integrations Sync customer data to Shopify metafields for native customer segmentation and personalization. **Status**: Available now Additional platform integrations (Adobe CDP, HubSpot, etc.) are on the roadmap. **Status**: Planned ## Key Concepts A unique identifier for each data field (e.g., `favorite_color`, `shirt_size`). Must be 2-64 characters, start with a lowercase letter, and contain only lowercase letters, numbers, and underscores. Auto-suggested from the question text but fully customizable. Once data has been synced to an external platform, certain configuration fields become locked: the field key, data type, multi-select mode, and choices override. This prevents breaking existing data on the external platform. Display name, description, and platform-specific settings like pinning can still be updated. External platform sync is always asynchronous. If Shopify is down or a sync fails, the user's challenge participation still succeeds. Failed syncs are automatically retried by the queue system. If two actions share the same field key, they must use the same data type. Nudj tracks field definitions per community and rejects conflicting type assignments. This ensures data integrity across your entire question library. ## Next Steps Learn how to enable and configure data hydration on your question actions in the admin panel. Set up Shopify customer metafield sync for native segmentation in your Shopify store. # Shopify Customer Data Sync Source: https://docs.nudj.cx/enterprise/customer-data-shopify Sync customer data from Nudj question actions to Shopify customer metafields for native segmentation and personalization Shopify Customer Data Sync pushes user answers from Nudj question actions directly into Shopify customer metafields. This enables you to use Shopify's native customer segment builder to target customers based on their Nudj engagement data — no CSV exports, no manual data entry. **Prerequisite**: Your community must have a linked Shopify store with the `write_customers` OAuth scope. Existing Shopify installations are automatically prompted for re-consent when the scope is added. ## How It Works On a question action, enable data hydration and configure the Shopify sync section. Nudj automatically creates the metafield definition on your Shopify store. When a user completes the action, Nudj stores the answer locally and queues an async job to sync it to Shopify. The queue job writes the resolved value to the customer's metafield in Shopify using the `$app:nudj` namespace. If the customer doesn't exist in Shopify (no `externalUserId`), the sync is silently skipped. Use Shopify's customer segment builder to create segments based on the synced metafield values. For example: "Customers where Favorite Color is Blue". ## Shopify-Specific Configuration When Shopify sync is enabled on a data hydration config, additional settings become available: ### Pin to Customer Detail When enabled, the metafield appears directly on the customer detail page in your Shopify admin. This makes the data immediately visible when viewing a customer profile. Pin fields that your support or sales team needs to see at a glance — like preference data or quiz results. Leave unpinned for fields used primarily for segmentation. ### Display Name and Description These values appear in the Shopify admin when viewing metafield definitions. Use clear, descriptive names so your Shopify team understands what each field represents. * **Display Name**: Shown as the field label (e.g., "Favorite Color") * **Description**: Shown as help text (e.g., "Answer from the Style Quiz challenge") ## Type Mapping Nudj data types map to Shopify metafield types automatically: | Nudj Type | Shopify Metafield Type | Notes | | - | - | - | | Text | `single_line_text_field` | Standard text metafield | | Integer | `number_integer` | Whole numbers | | Decimal | `number_decimal` | Decimal numbers (JSON format) | | Boolean | `boolean` | True/false values | | Date | `date` | ISO date string | For boolean expansion mode, each expanded field is always created as a `boolean` type in Shopify, regardless of the parent field's data type. ## Metafield Namespace All Nudj-synced metafields use the `$app:nudj` namespace. This is a Shopify app-reserved namespace that: * Prevents naming conflicts with other apps or custom metafields * Groups all Nudj data together in the Shopify admin * Is automatically associated with your Shopify app installation ## Boolean Expansion in Shopify Boolean expansion is particularly powerful with Shopify because Shopify's customer segment builder supports boolean filters natively. **Example**: A "Pick your interests" question with options: Sports, Music, Travel, Food, Tech This creates 5 metafield definitions: | Metafield Key | Type | Value (user selected Sports + Travel) | | - | - | - | | `interests_sports` | boolean | `true` | | `interests_music` | boolean | `false` | | `interests_travel` | boolean | `true` | | `interests_food` | boolean | `false` | | `interests_tech` | boolean | `false` | **Segment example**: `interests_sports = true AND interests_travel = true` finds customers interested in both sports and travel. Boolean expansion is limited to **5 options per action**. Each option consumes one metafield definition slot out of the 256 available per namespace. Plan your expansion fields carefully. ## Capacity Management Shopify allows a maximum of **256 custom metafield definitions** per namespace per resource type. Nudj checks this limit before creating new definitions. ### How Definitions Are Counted | Config | Definitions Used | | - | - | | Standard field (Text, Integer, etc.) | 1 | | Comma Separated multi-select | 1 | | Boolean Expansion (3 options) | 3 | | Boolean Expansion (5 options) | 5 | ### Approaching the Limit The admin panel warns when you're approaching the 256-definition limit. New configurations that would exceed the limit are rejected at save time with a clear error message. Use comma-separated mode instead of boolean expansion when you have many options or don't need per-option filtering. It uses only 1 definition regardless of option count. ## OAuth Scope Requirements Shopify Customer Data Sync requires the `write_customers` scope on your Shopify app installation. ### New Installations New Shopify app installations automatically include the `write_customers` scope. No additional setup is needed. ### Existing Installations Existing installations are prompted for re-consent automatically. When a merchant next accesses the Nudj Shopify app, Shopify's `authenticate.admin()` detects the scope difference and presents the consent screen. No manual action is needed from the merchant beyond accepting the updated permissions when prompted. ## Error Handling and Retries Shopify sync is designed to be resilient: | Scenario | Behavior | | - | - | | Shopify API temporarily unavailable | Job retried automatically (up to 5 times with backoff) | | Customer has no `externalUserId` | Sync silently skipped — data still stored in Nudj | | Community has no Shopify credentials | Sync silently skipped | | Rate limit exceeded | Job retried after backoff | | Invalid metafield value | Marked as failed, not retried | | Type coercion failed | Nudj data stored, Shopify sync skipped | The user's participation in the challenge is **never** blocked or affected by Shopify sync status. All sync happens asynchronously after participation is confirmed. ## Segmentation Examples Once data is synced, use Shopify's customer segment builder: | Use Case | Segment Query | | - | - | | Customers who prefer blue | `favorite_color = 'Blue'` | | Customers who rated 4+ stars | `product_rating >= 4` | | Customers interested in sports | `interests_sports = true` | | Customers with a birthday in March | `customer.metafields.app_nudj.birthday.month = 3` | | Customers who answered the quiz | `quiz_answer IS NOT NULL` | These segments can then be used for targeted email campaigns, personalized storefront experiences, and marketing automation within Shopify. ## Troubleshooting * Verify the user has an `externalUserId` (linked Shopify customer) * Check that the community has active Shopify credentials * Ensure the `write_customers` scope is granted * Allow a few seconds for the async queue job to process * Check that you saved the action after enabling data hydration * Verify the field key doesn't conflict with an existing definition of a different type * Check the 256-definition capacity limit * The definition sync job runs asynchronously — allow a few seconds * Ensure **Pin to Customer Detail** is enabled in the hydration config * The pin setting can be updated even after first sync * Verify the metafield definition exists in Shopify admin under **Settings > Custom data > Customers** * Check that the question has 5 or fewer options * Verify sufficient definition capacity (each option = 1 definition) * Ensure no other action uses the same field key with a different mode ## Next Steps Detailed guide to all data hydration configuration options. Understand the full data hydration architecture and supported action types. # Embedding Modes Source: https://docs.nudj.cx/enterprise/embedding-integration Four ways Nudj renders inside your surface: Shopify storefront widgets, the universal embed widget, iframe, and mobile webview. Nudj ships four distinct embedding modes. Pick one based on where your users are and what authentication you already have. Theme app extension blocks a merchant drops into their storefront. First-class path if your storefront is Shopify. Cross-platform embed script + admin customisation panel (PR #1971). Works on any CMS or hand-rolled site. Host the Nudj user app in an ` ``` ### CSP requirements The Nudj user app sets a `Content-Security-Policy` with a `frame-ancestors` directive. For the iframe to render on your domain you must either: * Be embedded from a Shopify storefront domain — PR #1638 added Shopify hosts to the allowlist. * Use the universal embed allowance — PR #2333 opened `frame-ancestors` for universal embedding. * Ask your account manager to add your host to the organisation's allowlist. Authenticate the iframe user via [OIDC SSO](/enterprise/sso-authentication) or [API Link](/enterprise/api-link-user-authentication) — cross-origin iframe cookies are subject to the user's browser settings. ## 4. Mobile webview Wrap Nudj in a native webview when your surface is a mobile app. The only supported authentication mode here is [API Link](/enterprise/api-link-user-authentication) — your native app issues a Nudj token server-side and passes it into the webview. ```swift theme={null} import UIKit import WebKit class NudjViewController: UIViewController { var webView: WKWebView! override func loadView() { let config = WKWebViewConfiguration() webView = WKWebView(frame: .zero, configuration: config) view = webView } override func viewDidLoad() { super.viewDidLoad() let token = fetchNudjApiLinkToken() // server-issued let url = URL(string: "https://your-org.nudj.cx/api/link?token=\(token)")! webView.load(URLRequest(url: url)) } } ``` ```kotlin theme={null} import android.webkit.WebView import androidx.appcompat.app.AppCompatActivity class NudjActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val webView = WebView(this) webView.settings.javaScriptEnabled = true val token = fetchNudjApiLinkToken() // server-issued webView.loadUrl("https://your-org.nudj.cx/api/link?token=$token") setContentView(webView) } } ``` ## Picking a mode | Your surface | Recommended mode | | - | - | | Shopify storefront | Shopify storefront widgets | | Non-Shopify ecommerce / CMS | Universal embed widget | | Custom web portal, user already authenticated | Iframe + API Link | | Custom web portal, user logging in fresh | Iframe + OIDC SSO | | Native iOS / Android app | Mobile webview + API Link | ## Next steps Setup script and admin panel for the cross-platform embed. Widget catalog for theme app extensions. > Last reviewed: 2026-04 · Related PRs: #1971, #2333, #1638 # Enterprise Integration Overview Source: https://docs.nudj.cx/enterprise/integration-overview Complete guide to the three core integration areas: authentication, access, and reward delivery for enterprise Nudj deployments When businesses integrate Nudj into their ecosystem, there are three fundamental questions that drive the implementation approach. Understanding these areas upfront ensures a smooth integration that aligns with your business goals and technical constraints. ## The Three Core Integration Areas **How do users log in?** Choose between OAuth/SSO for new logins or API Link for pre-authenticated users from your platform. **How do users access Nudj?** Decide between iframe embedding, in-app browser, or standalone access based on your user experience requirements. **How do rewards reach users?** Configure reward delivery to external wallets, loyalty systems, or your existing reward infrastructure. ## Integration Decision Flow Understanding which approach fits your business model is crucial for a successful implementation: **Start here**: How are your users currently authenticated? * **Already logged into your platform?** → Use [API Link Authentication](/enterprise/api-link-user-authentication) * **Need them to log in fresh?** → Use [OAuth/SSO Authentication](/enterprise/oauth-authentication) * **Want enterprise-wide SSO?** → Review [Enterprise SSO Options](/enterprise/sso-authentication) **Next**: How should users access the Nudj experience? * **Seamless in-app experience?** → Consider [Iframe Embedding](/enterprise/embedding-integration#iframe-embedding) * **Native app integration?** → Use [In-App Browser](/enterprise/embedding-integration#in-app-browser) * **Separate engagement portal?** → Set up [Standalone Access](/enterprise/embedding-integration#standalone-access) **Finally**: Where should earned rewards go? * **Your existing wallet/loyalty system?** → Set up [External Wallet Integration](/enterprise/reward-integration) * **Real-time notifications needed?** → Configure [Webhook Rewards](/enterprise/webhook-rewards) * **Custom reward fulfillment?** → Explore [Advanced Reward Workflows](/enterprise/reward-integration#custom-fulfillment) ## Common Integration Patterns ### Pattern 1: E-commerce Integration **Scenario**: Retail brand with existing loyalty program **API Link Authentication**: Users are already logged into your e-commerce platform * Seamless transition from shopping to engagement activities * No additional login required * Maintains session context **Iframe Embedding**: Nudj challenges embedded in loyalty section * Consistent brand experience * Native platform navigation maintained * Mobile-responsive design **External Wallet Integration**: Points flow to existing loyalty system * Webhook notifications trigger point deposits * Unified reward balance for customers * Existing redemption infrastructure used ### Pattern 2: Mobile App Integration **Scenario**: Mobile-first platform with in-app engagement **OAuth/OIDC**: Users log in through your identity provider * Consistent authentication experience * Enterprise security compliance * Single sign-on capabilities **In-App Browser**: Native web view within mobile app * Feels like part of your app * Access to device features * Smooth user experience **Webhook Integration**: Real-time reward notifications * Instant gratification for users * Push notifications on reward earning * Synchronized with in-app currency systems ### Pattern 3: Enterprise Portal **Scenario**: B2B platform with employee engagement **Enterprise SSO**: Integration with Active Directory/LDAP * Corporate identity management * Automatic user provisioning * Role-based access control **Standalone Portal**: Dedicated engagement subdomain * Professional, branded experience * Custom domain setup * Complete white-label control **Custom Integration**: HR system integration for recognition * Performance tracking integration * Manager visibility into engagement * Corporate reward fulfillment ## Implementation Roadmap ### Phase 1: Planning & Architecture (Week 1-2) * Map current user authentication flow * Define desired user experience * Identify existing reward systems * Review technical constraints * Review API documentation * Plan integration architecture * Identify required endpoints * Set up development environment ### Phase 2: Core Integration (Week 3-4) Configure your chosen authentication method with comprehensive testing Set up user access pattern with responsive design and security Connect reward delivery to your existing systems ### Phase 3: Testing & Optimization (Week 5-6) * **User Acceptance Testing**: Validate complete user journey * **Performance Testing**: Ensure scalability and responsiveness * **Security Review**: Verify authentication and data protection * **Analytics Setup**: Configure tracking and reporting ### Phase 4: Launch & Monitoring (Week 7+) * **Gradual Rollout**: Start with pilot user group * **Monitor Performance**: Track engagement and technical metrics * **Gather Feedback**: Collect user and stakeholder input * **Iterate & Improve**: Refine based on real-world usage ## Getting Started Checklist * [ ] Nudj Enterprise subscription activated * [ ] Admin access to Nudj platform configured * [ ] Development environment set up * [ ] API credentials generated and secured * [ ] Subdomain and branding configured * [ ] User authentication flow mapped * [ ] Access pattern decided (iframe/browser/standalone) * [ ] Reward delivery method chosen * [ ] Technical architecture reviewed * [ ] Timeline and milestones defined * [ ] Authentication method configured and tested * [ ] User access implementation completed * [ ] Reward integration connected * [ ] End-to-end testing completed * [ ] Launch plan approved ## Need Help Deciding? If you're unsure which integration approach fits your business: **Enterprise Service**: Work with our solutions architects to design your optimal integration approach Contact: [enterprise@nudj.cx](mailto:enterprise@nudj.cx) **Engineering Support**: Get hands-on guidance for complex integration requirements Include: Current architecture, user flow diagrams, technical constraints ## Related Documentation **Overview**: Enterprise capabilities and strategic features **Shopify**: Partner install, storefront widgets, embedded admin, rewards, and webhook topics. **Branding**: Complete brand control and customization # OAuth Integration Setup Source: https://docs.nudj.cx/enterprise/oauth-authentication Configure your OAuth 2.0 / OpenID Connect (OIDC) provider to work with Nudj Platform This guide explains how to configure your OAuth 2.0 / OpenID Connect (OIDC) provider so it works with Nudj. This method is suitable for scenarios where users arrive directly at Nudj (not authenticated) and press 'log in'. ## Quick Start Ensure users will arrive directly at Nudj not authenticated and log in there. If users are already authenticated elsewhere, use [API Link User Token Authentication](/enterprise/api-link-user-authentication) instead. Navigate to **Organisation Settings → Authentication** in the [admin panel](https://admin.nudj.cx/admin/settings/organisation?tab=authentication) and add your OAuth provider details. If your provider publishes an OIDC discovery document at `${issuer}/.well-known/openid-configuration`, paste the issuer URL and Nudj will auto-fetch the endpoints and advertised scopes (PR #1466). Otherwise fill the fields manually: * **issuer** (OIDC base URL) * **clientId** * **clientSecret** * **Required scopes**: `openid`, `profile`, `email` (optional: `offline_access`). Custom scopes beyond the OIDC defaults are supported (PR #1466). Save the configuration — Nudj generates your `oidcConfig.id` and displays it directly in the Authentication tab (PR #1433). No more emails to support. Copy the `oidcConfig.id` from the Authentication tab and build the Nudj redirect URI (exact match, no trailing slash): ``` https://${rootDomain}/api/auth/callback/organisation-oidc-${oidcConfig.id} ``` Find `${rootDomain}` in **Organisation Settings → Domains** in the [admin panel](https://admin.nudj.cx/admin/settings/organisation?tab=domains). Paste the fully-resolved URL into your OAuth/OIDC provider's allowed redirect list. Save changes in your OAuth provider and test login in your Nudj environment. If you see a `PKCE cookie missing` error on callback, confirm that third-party cookies are permitted on the callback origin — this was tightened in PR #1538. ## Troubleshooting PR #1538 enforces PKCE on the OIDC callback. The short-lived PKCE cookie is set on the auth initiation origin and must survive the round-trip back to `${rootDomain}/api/auth/callback/...`. Most failures come from: * A mismatched cookie domain between the initiation and callback hosts (e.g. initiating on a custom domain but the redirect lists the `.nudj.cx` host). * Aggressive third-party cookie blockers in embedded webviews. Ensure the domain you kick login off from is the same one listed in the provider's redirect URI. Nudj requests the scopes you configured plus `openid`, `profile`, `email`. If your provider rejects any of them, remove the unsupported scope from the Authentication tab and re-save. The auto-fetch flow (PR #1466) will surface the scopes your provider actually advertises. Providers match the redirect URI as an exact string. Confirm no trailing slash, correct scheme (`https`), and that `${oidcConfig.id}` is the value currently shown in the Authentication tab — regenerating the config produces a new ID. ## When to Use This Method * Users arrive directly at Nudj not authenticated * Users initiate login from Nudj interface * Standard authorization flow required * Redirects to your login system acceptable * Users already authenticated elsewhere * Seamless session continuation needed * No login screen should be shown * Direct JWT-based authentication preferred ## Related Authentication Methods **Alternative Method**: For users already authenticated elsewhere who need seamless session continuation **Overview**: Complete guide to all enterprise authentication options and patterns **Next Steps**: Understand access patterns and reward delivery after authentication setup # Enterprise Platform Overview Source: https://docs.nudj.cx/enterprise/platform-overview What the Enterprise tab covers: authentication, embedding, white-label, webhooks, customer data hydration, and the Shopify integration surface. The Enterprise tab collects the integration surfaces Nudj exposes to your engineers, security team, and merchandising team. Everything here is implemented today — no roadmap or sales collateral. Use this page as an index into the tab; each section links to the page with the concrete setup steps. ## Authentication How users sign in or have their session handed over from your system. Single sign-on via OpenID Connect. OIDC is the only protocol supported — see the dedicated page for what is and isn't available. Step-by-step provider configuration with auto-discovery and self-serve `oidcConfig.id`. JWT-based session handoff when the user is already authenticated in your system. ## Embedding How the Nudj experience renders inside your surface. Shopify storefront widgets, the universal website embed, iframe, or mobile webview — pick one. Cross-platform embed for any website, with an admin customisation panel (PR #1971). CNAME setup, SSL, theme variables. No native mobile app product — see the page for the honest scope. ## Shopify The Shopify integration is the largest enterprise surface in the last year (55% of enterprise PRs). If your organisation runs on Shopify, start here. What the Shopify app provides end-to-end — Partner install, storefront widgets, embedded admin, webhooks. Every theme app extension widget block Nudj publishes — loyalty, Ways to Earn, referrals, points-for-spend, checkout, customer account. ## Rewards + Webhooks How rewards reach your downstream systems, and how webhook-based events are emitted. Webhook-driven, pull-based, direct integration, or Shopify-backed rewards. HMAC signing, retry semantics, Node/Python endpoint examples. Reference of events Nudj emits (including the 16 Shopify-driven events from PR #1780). Inspect retry state and delivery history via the Admin API. ## Customer data hydration Push Nudj-captured data into your downstream CRM / commerce system. Two-layer model: generic custom data + platform sync. Field keys, type coercion, boolean expansion. Native Shopify customer metafields backed by question-action answers. ## Privacy + compliance OneTrust integration for analytics providers (PR #973). > Last reviewed: 2026-04 # Reward Integration & Delivery Source: https://docs.nudj.cx/enterprise/reward-integration Complete guide to integrating Nudj rewards with external wallets, loyalty systems, and custom fulfillment platforms When users earn rewards in Nudj, they expect those rewards to appear in their existing wallets and loyalty systems. This guide covers how to connect Nudj's reward system with your external infrastructure for seamless reward delivery. **Quick Decision**: Have an existing loyalty system? Use **API Integration**. Need real-time notifications? Use **Webhooks**. Building something custom? Use **Direct Integration**. ## Reward Delivery Methods **Best for**: Real-time notifications, event-driven systems **Pros**: Instant delivery, reliable processing, automatic retries **Cons**: Requires endpoint setup, security considerations **Best for**: Existing loyalty systems, scheduled processing **Pros**: Full control, batch processing, error handling **Cons**: Polling required, potential delays **Best for**: Custom systems, specialized requirements **Pros**: Maximum flexibility, custom logic, deep integration **Cons**: Complex setup, maintenance overhead **Best for**: Shopify storefronts Discount codes, free-product-via-discount-function redemptions, checkout-link redemptions, and product sync from Shopify into the Nudj reward catalog. See [Shopify Rewards](/enterprise/shopify/rewards). ## Understanding Nudj Rewards **Two main categories of rewards in Nudj**: **Direct rewards that users receive immediately**: * **Points/Currency**: Loyalty points, credits, tokens * **Digital Items**: Coupons, discount codes, digital content * **Physical Items**: Merchandise, gift cards (requires fulfillment) * **Access Rights**: Premium features, exclusive content ```json theme={null} { "type": "asset", "reward": { "id": "reward_123", "name": "10% Discount Code", "type": "digital_coupon", "value": "SAVE10", "description": "10% off your next purchase" } } ``` **Competition entries that give users chances to win**: * **Prize Draws**: Monthly competitions, grand prizes * **Instant Win**: Scratch cards, spin-to-win mechanics * **Tiered Competitions**: Multiple prize levels * **Limited Availability**: First-come-first-served rewards ```json theme={null} { "type": "entry", "reward": { "id": "entry_456", "name": "Monthly Prize Draw Entry", "competition_id": "comp_789", "draw_date": "2024-03-01T00:00:00Z", "prize_description": "£500 Shopping Voucher" } } ``` ## Webhook Integration Real-time reward delivery using webhooks for immediate processing when users earn rewards. ### Implementation Guide Create a secure endpoint to receive reward notifications: ```javascript theme={null} app.post('/webhooks/nudj/rewards', (req, res) => { // Verify webhook signature const signature = req.headers['x-nudj-signature']; if (!verifySignature(req.body, signature)) { return res.status(401).json({ error: 'Invalid signature' }); } // Process reward processReward(req.body) .then(() => res.status(200).json({ success: true })) .catch(error => res.status(500).json({ error: 'Processing failed' })); }); ``` Set up the webhook configuration in your Nudj admin panel: ```json theme={null} { "webhook_config": { "url": "https://your-api.com/webhooks/nudj/rewards", "events": [ "reward.earned", "reward.delivered", "reward.failed" ], "authentication": { "type": "hmac_sha256", "secret": "your-webhook-secret" } } } ``` ### Real-World Integration Examples **Integration with mobile app loyalty systems**: ```javascript theme={null} async function handleMobileWalletDelivery(user, reward) { // Add points to mobile wallet const walletResult = await mobileWalletAPI.creditAccount({ customer_id: user.external_id, amount: reward.value, currency: reward.currency, transaction_type: 'engagement_reward' }); // Trigger push notification await pushNotificationService.send({ device_tokens: user.device_tokens, notification: { title: 'Reward Earned!', body: `${reward.value} points added to your wallet` } }); } ``` **Integration with e-commerce platforms and existing loyalty systems**: ```javascript theme={null} async function handleEcommerceLoyalty(user, reward) { // Add to loyalty program const loyaltyResult = await ecommerceAPI.loyalty.addPoints({ customer_email: user.email, points: reward.value, program_tier: await getTierForUser(user.external_id) }); // Create personalized discount if threshold reached if (loyaltyResult.points_total >= loyaltyResult.next_reward_threshold) { const discount = await createPersonalizedDiscount(user, loyaltyResult); await emailService.send({ to: user.email, template: 'milestone_reward', data: { discount_code: discount.code } }); } } ``` ## API Integration Pull-based integration where your system periodically retrieves earned rewards from Nudj APIs. ### Implementation Guide Configure API access for retrieving rewards: ```bash theme={null} NUDJ_API_BASE_URL=https://your-company.nudj.cx/api NUDJ_API_TOKEN=your-api-token ``` Create a service to regularly check for new rewards: ```javascript theme={null} async function pollForRewards() { const response = await this.apiClient.get('/rewards/pending', { params: { since: this.lastProcessedTimestamp, limit: 100, status: 'pending_delivery' } }); const rewards = response.data.rewards; console.log(`Found ${rewards.length} pending rewards`); for (const reward of rewards) { await this.processReward(reward); } } ``` ## Next Steps **Deep Dive**: Detailed webhook configuration and event handling **Prerequisites**: Set up user authentication before reward delivery **Reference**: Full list of webhook event types emitted by Nudj. # Shopify Customer Accounts Source: https://docs.nudj.cx/enterprise/shopify/customer-accounts Customer account linking, the Members page with gamification stats, and the CSP allowlist for Shopify domains. Shopify's new customer account system hosts the logged-in experience for shoppers. Nudj plugs into that surface so a merchant's gamification stats appear inside the Shopify-hosted account UI — the shopper sees points, tier, achievements, and the Ways to Earn catalog without leaving Shopify. ## Account linking PR #1407 implemented **Shopify customer account linking** — mapping a Shopify customer id to a Nudj user id. Once linked: * Shopify events for the customer resolve to the Nudj user. * Rewards, points, and achievements attributed to the Shopify customer land on the matching Nudj user profile. * Customer Data Hydration (PR #2184) can write back to the Shopify customer's metafields. PR #1414 shipped the **auth link flow** — the concrete UX that walks a Shopify customer through linking to their Nudj account on first interaction. ## Customer Account extensions Shopify customer accounts allow apps to ship extensions (`apps/shopify/extensions/customer-account-*`). Nudj ships: * `customer-account-loyalty-hub` — a customer-account-scoped loyalty hub view. * `customer-account-profile-block` — a profile block that renders on the customer account home. Both are configured through the Shopify admin's Customer Accounts settings. ## Members page PR #1689 shipped a **Members page** inside the Shopify embedded admin — a merchant-facing view of customers with their gamification stats (points, tier, recent achievements). Merchants use it to triage power users, check a specific customer's state, or export a segment. Admin members page with linked Shopify customers ## CSP allowlist for Shopify domains The Nudj user app's CSP `frame-ancestors` directive was tightened in earlier work; PR #1638 added Shopify storefront domains (including customer-account origins) to the allowlist so the customer-facing surfaces render inside Shopify without being blocked. PR #2333 later opened CSP further for the universal embed widget. ## Mobile optimisation PR #1905 optimised the entire Shopify embedded admin (including the Members page) for 390px viewports. Merchants using Shopify's mobile merchant app can manage the programme from a phone. ## Client-side data fetching PR #2128 migrated the embedded admin (Members page included) to client-side data fetching so navigation between pages is instant. PR #2141 was a wider consistency pass that aligned the page header and empty-state shapes across all admin pages. ## Next steps Customer-facing widget blocks including the loyalty account widget. Push question-action answers into Shopify customer metafields for segmentation. > Last reviewed: 2026-04 · Related PRs: #1407, #1414, #1638, #1689, #1905, #2128, #2141, #2333 # Onboarding Checklist Source: https://docs.nudj.cx/enterprise/shopify/onboarding-checklist The server-verified onboarding checklist Shopify merchants see after install, plus the wizard modal and home dashboard. After install, Nudj's Shopify embedded admin presents a merchant with a getting-started checklist. Each step verifies completion server-side so a merchant cannot skip past a required configuration. Admin dashboard for a Shopify-connected community ## The checklist PR #2020 replaced the client-side onboarding wizard with a **server-verified checklist**. Each item's completion state is derived from the merchant's data, not a flag the user can dismiss. That way: * The checklist shows accurate state even after a merchant navigates away and returns. * Admins can't mark a step complete before the underlying config exists. * The checklist re-appears if a merchant deletes the backing config later. The starting set of steps derives from the embedded admin pages scaffolded by PR #1722 and covers the minimum configuration needed for the storefront widgets and rewards to function: * Bind the Shopify store to a Nudj community. * Pick a theme preset for the widgets. * Enable the loyalty widget on the storefront. * Configure a baseline reward. * Configure at least one achievement. ## Wizard modal PR #2007 added a wizard modal that launches from the getting-started page. The modal walks merchants through the first-configuration flow without leaving the checklist, then re-runs the server-side verification so steps tick off in place. ## Home dashboard Once past onboarding, the merchant's home screen is an activity chart summary (PR #1985) — running totals of orders, challenges completed, points earned, referrals. Clicking a metric navigates to the detail page for that surface. ## Access and scheduling PRs #2001 and #1982 **hide** the access and scheduling selectors in settings while those features remain in preview. Merchants instead see a "coming soon" teaser in the settings sidebar (PR #1983). ## Pages consistency PR #2141 aligned all embedded admin pages to a consistent layout, header pattern, and empty-state shape. PR #2128 migrated data fetching to client-side so navigation between pages is instant. ## Mobile support PR #1905 optimised the embedded admin for 390px viewports — iPhones running Shopify's merchant mobile app see a full checklist and dashboard, not a broken desktop layout. ## Next steps The widget blocks merchants add to their theme. Configure the baseline reward the checklist asks for. > Last reviewed: 2026-04 · Related PRs: #1722, #1905, #1982, #1983, #1985, #2001, #2007, #2020, #2128, #2141 # Shopify Integration Overview Source: https://docs.nudj.cx/enterprise/shopify/overview How the Nudj Shopify integration works end-to-end: Partner install, embedded admin, storefront widgets, Admin API access, and webhook registration. Nudj is a Shopify Partner app. Merchants install it from the Shopify app store; install provisions an embedded admin app, a theme app extension for storefront widgets, programmatic webhook registration, and a binding from the Shopify store to a Nudj community. ## What you get after install Merchants manage their Nudj loyalty programme from inside the Shopify admin. Home dashboard (PR #1985), onboarding checklist (PR #2020), settings, rewards, achievements, referrals — no switching UIs. Merchants drop Nudj widget blocks into their storefront via the theme editor. Loyalty FAB, Ways to Earn, Refer-a-Friend, points-for-spend nudges, checkout points, customer account — every block themed through `community.theme`. Discount codes, free products via Shopify discount functions, checkout-link redemptions, and product sync from the Shopify catalog into Nudj rewards. Nudj programmatically registers Shopify webhooks on install (PR #1684) and consumes 16 event topics (PR #1780) to trigger achievements. ## How it connects together The merchant approves the requested scopes in Shopify's consent screen. Nudj's install flow (PR #1402) kicks off deployment pipelines (PRs #1427, #1431, #1432). Shopify issues an offline access token. Nudj encrypts it at rest (PR #1484) and stores it against the merchant's Shopify session (PR #1833 — Shopify Sessions Service). PR #1819 implemented the store-to-community binding. Subsequent events from this store resolve to the bound community (PR #1654). PR #1684 registers Shopify webhooks on install. Topics include order placement, subscription events, customer creation, and more — 16 new topics from PR #1780 drive achievement triggers. The merchant opens the Nudj app inside Shopify admin. Admin API access (PR #1438) authenticates the embedded UI against Nudj's backend. PR #1863 added 9 read-only Shopify Admin API endpoints for the embedded UI to consume. Merchants open the theme editor and add Nudj widget blocks. Per-block theming (PR #1646), theme presets (PR #1886), and widget preview URL selection (PRs #1896, #1899, #1637) make the customisation experience predictable. ## Nudj admin vs Shopify embedded admin Both UIs exist. The Shopify embedded admin (inside `admin.shopify.com/store/...`) is the merchant-facing UI and covers the common surfaces — rewards, referrals, widgets, achievements. The full Nudj admin (`admin.nudj.cx`) remains available for organisation-level features and advanced settings. See the merchant-facing [Shopify onboarding checklist](/enterprise/shopify/onboarding-checklist) for what's surfaced in each UI. ## End-to-end status PR #1474 marked the Shopify integration "end-to-end complete" after PR #1482's quality-of-life fixes landed. Since then the widget catalog has grown substantially (PRs across #2190, #2163, #2220, #2224, #2238, #2253, #2387), and the embedded admin pages went through a consistency pass (PR #2141) and migrated to client-side data fetching for instant navigation (PR #2128). ## Next steps What merchants see on day one. The widget catalog. > Last reviewed: 2026-04 · Related PRs: #1402, #1427, #1431, #1432, #1438, #1474, #1482, #1484, #1654, #1684, #1819, #1833, #1863 # Promotions and Multipliers Source: https://docs.nudj.cx/enterprise/shopify/promotions-and-multipliers Points multipliers, streak rewards, achievement templates, and leaderboards inside the Shopify embedded admin. The Shopify embedded admin surfaces three loyalty mechanics merchants configure without engineering support: points multipliers, streak rewards, and pre-built achievement templates. Each ships as a first-class page with inline setup. ## Points multiplier promotions PR #2010 added a **points multiplier promotions** page to the Shopify admin. Merchants can define time-windowed promotions — e.g. 2× points on Fridays, 3× points during Black Friday — and the multiplier applies to any points-earning action within the window. Under the hood, multiplier promotions layer on top of the [effects system](https://github.com/nudj-digital/nudj/blob/main/packages/services/service/points-transaction/helpers/points-multiplier.ts) — user-scoped modifiers stored on `communityData.effects[]`. Multiple active multipliers don't stack; the highest wins per type. Create a New Reward modal showing XP Boost and Points Boost multiplier types ## Streak rewards PR #2000 added **streak rewards** to the Shopify admin. Merchants define a streak-based reward — complete N consecutive days of action X, earn reward Y. PR #2005 wired the streak toggle (and achievement toggle) on/off from the settings page so merchants can enable the mechanic per community. Streak configuration screen with trigger events and preview ## Achievement template library PR #2133 shipped an **achievement template library** tailored for simplified D2C setup. Merchants pick from pre-built achievement definitions (first purchase, review submission, social follow, etc.) instead of authoring each from scratch. This is the fastest path from install to a populated programme. ## Game settings PR #1986 added a **game settings modal** with a tabbed config UI. Games (birthday challenges, badge collections, etc.) share a configuration surface so merchants don't have to learn a new UI per game type. ## Leaderboard templates PR #2220 added a **leaderboard template library** with an edit-in-admin deep-link — merchants can preview a template inside the Shopify admin, then jump straight into the full Nudj admin to tweak advanced settings. ## Putting it together A typical mid-tier Shopify loyalty programme using these levers: 1. Apply a **holiday points multiplier** (PR #2010) for a sales event. 2. Enable a 7-day **streak reward** (PR #2000, #2005) to drive return visits. 3. Seed the programme with 3–5 **achievement templates** (PR #2133) from the library. 4. Surface the [Ways to Earn widget](/enterprise/shopify/storefront-widgets) on the storefront so shoppers see what they can earn. ## Next steps Back the achievements with Shopify rewards. Surface the mechanics on the storefront. > Last reviewed: 2026-04 · Related PRs: #1986, #2000, #2005, #2010, #2133, #2220 # Referral Program (Shopify) Source: https://docs.nudj.cx/enterprise/shopify/referral-program Configure the referral program from the Shopify embedded admin: referrer + referee rewards, landing URL, and the analytics dashboard. Nudj's referral system lives on the community (`community.referralConfig`). The Shopify embedded admin surfaces the configuration so merchants can set it up without leaving Shopify. The non-Shopify referral admin UX is documented in the Control Room tab. This page covers the Shopify surface specifically. ## Configuration UI PR #1733 shipped the referral program UI and loyalty settings inside the Shopify embedded admin. Merchants configure: * **Referrer reward** — what the existing user gets when a friend signs up (or hits the configured qualification event). * **Referee reward** — what the newly-referred user gets. * **Qualification event** — which action a referred user must complete for the referrer to earn. Behind the scenes this writes to `community.referralConfig`, which the Referral achievement (a backing achievement type) uses to fire the two distribution events. ## Landing URL PR #2240 consolidated the referral landing URL handling and shipped a **standalone referral widget** alongside. Merchants get a single URL they can share through any channel; the URL routes through the Nudj referral resolver and hands off to Shopify / the loyalty UI. PR #2126 fixed an early issue where the referral page failed to load in the Shopify embedded admin — the fix ensures the admin can always read/write the referral config. ## Refer-a-Friend widget PR #2253 added a dedicated **Refer-a-Friend** storefront widget block. Drop it into any page via the theme editor; the widget renders the user's personal referral link and a call-to-action. See [storefront widgets](/enterprise/shopify/storefront-widgets) for the full widget catalog. ## Analytics dashboard PR #2084 shipped a **referral analytics dashboard** across Shopify, API, and admin surfaces. The dashboard covers: * Number of invites sent. * Referral conversion rate. * Revenue attributed to referred customers. * Top referrers leaderboard. Referral Programme configuration page ## How rewards distribute When a referee qualifies, two distribution events fire: 1. **Referrer event** — credits the configured reward to the referring user. 2. **Referee event** — credits the configured welcome reward to the new user. Both events are scoped to the community's referral config. Referral achievements are isolated from standard achievement APIs — they only surface where referral-specific queries are made. ## Next steps Storefront block that surfaces the referral link. The account surface referred users land on. > Last reviewed: 2026-04 · Related PRs: #1733, #2084, #2126, #2240, #2253 # Shopify Rewards Source: https://docs.nudj.cx/enterprise/shopify/rewards Shopify-backed reward types: discount codes, checkout links, product sync, and the free-product discount function. Nudj supports four rewards patterns backed by a linked Shopify store. All four are configurable from the Nudj admin panel and the Shopify embedded admin. ## Shopify reward type PR #1447 introduced the **Shopify reward type** in the admin panel's reward picker. A Shopify reward is backed by a specific product, discount, or redemption link in the merchant's Shopify store — not a Nudj-internal asset. PR #1378 added Shopify to the platform third-party config table that gates which reward types are available per community. PR #1461 fixed config checks that ran during reward upsert so partially-configured Shopify rewards can't be saved. ## Reward backends Backed by a Shopify discount code created server-side when the reward is earned. Redeemable at checkout. **PR #1492** — Nudj generates a Shopify checkout link (`/cart/...`) with the discount pre-applied. The user taps the link from their reward, lands in Shopify checkout already discounted. **PR #2160** — a Shopify discount function the merchant installs (`apps/shopify/extensions/nudj-free-product`). When the user reaches checkout with the reward claimed, the function grants the configured SKU for free. **PR #1459** — Nudj syncs the merchant's product catalog. Admins pick products from the synced catalog to back a reward without leaving the Nudj admin. PR #1493 simplified the sync modal UX. ## Backend creation flow PR #1467 formalised the server-side reward creation flow. When a merchant saves a Shopify reward, Nudj: 1. Validates the linked Shopify config (discount id, product id, or redemption path). 2. Calls Shopify to create or reserve the backing object. 3. Persists the reward against the community. ## Unlinking rewards PR #1992 added an individual unlink option for discount-backed rewards — merchants can detach the Shopify object from a single reward without removing the entire Shopify integration. ## Reward authoring UX Two UX PRs shape how rewards are edited from the Shopify embedded admin: * PR #1974 — consolidated rewards into a single unified card (replacing the previous split UI). * PR #1981 — inline reward form modal for giveaway and exclusive rewards, so merchants stay in context instead of opening a new screen. ## Configuration precedence A reward can reference a product, a discount, or a checkout link. When multiple are configured, the runtime resolves in this order: checkout link → discount function (free product) → discount code → synced product metadata. Picking a single backing is the recommended path; multi-backing is an advanced pattern surfaced only when your merchandising team explicitly requires it. ## Next steps Points multipliers, streak rewards, and achievement templates. Higher-level integration patterns including webhook-driven delivery. > Last reviewed: 2026-04 · Related PRs: #1378, #1447, #1459, #1461, #1467, #1492, #1493, #1974, #1981, #1992, #2160 # Storefront Widget Catalog Source: https://docs.nudj.cx/enterprise/shopify/storefront-widgets The Nudj Shopify theme app extension — what each widget block does, where it goes on the storefront, and how it is configured. Nudj ships a Shopify **theme app extension** (`apps/shopify/extensions/widget`). Merchants open the theme editor and drop Nudj widget blocks into their storefront. Each block renders a themed Nudj widget backed by the Nudj user app, wired to the `community.theme` configured in the Shopify embedded admin (PR #1646). This page is the reference catalog — one entry per block. ## Shared theming PR #1646 wired the Shopify widgets to `community.theme`. The merchant edits theme variables once in the embedded admin; every widget picks them up. PR #1886 added ready-to-use theme presets merchants can apply with one click, with unified config save back to `community.theme`. Widget preview URLs resolve via: * PR #1637 — use the first custom domain for the widget app URL. * PR #1896 — CSP + env override for non-production previews. * PR #1899 — prefer `customDomains` over the generic `userDomain` for in-admin previews. Widget schema names are Shopify-limited to 25 characters — PR #2431 shortened any exceeding schema names. Widget settings export ordering was aligned in PR #1845 so theme editor panels render fields consistently. ## Loyalty widget (FAB) **Files**: `extensions/widget/blocks/loyalty-fab.liquid` · **PRs**: #1634, #1635, #2256, #2045 A floating action button fixed to the viewport corner. Opens the full loyalty experience (points, challenges, rewards) in an in-storefront panel. * PR #1634 — initial scaffold. * PR #1635 — per-storefront configurability (admin can theme + toggle). * PR #2256 — improved error states, sizing, and link behaviour. * PR #2045 — fixed an issue where the FAB was stuck at `opacity: 0`. ## Simplified widget variant **PRs**: #1659, #1674 A lighter variant of the loyalty widget for minimal storefronts. PR #1674 added achievements to this variant. ## Loyalty account widget **Files**: `extensions/widget/blocks/loyalty-account.liquid` · **PR**: #2224 A block merchants place on storefront customer account pages (gamification stats, points balance, tier). Pairs with the [customer accounts](/enterprise/shopify/customer-accounts) integration. ## Ways to Earn widget **Files**: `extensions/widget/blocks/ways-to-earn.liquid` · **PRs**: #2238, #2257 Renders a catalog of active challenges/actions on any page. PR #2238 shipped the block, PR #2257 revamped it with consistent theming. ## Refer-a-Friend widget **Files**: `extensions/widget/blocks/referral-widget.liquid` · **PR**: #2253 Referral CTA widget. Generates the referral link, shows referral status, integrates with the [Shopify referral program](/enterprise/shopify/referral-program). ## Standalone referral widget **PR**: #2240 The consolidated referral landing URL work also shipped a **standalone** referral widget — usable outside of the main loyalty FAB for merchants who want referral prominence on specific pages. ## Points-at-checkout widget **Files**: `extensions/widget/blocks/points-at-checkout.liquid`, `points-at-checkout-drawer.liquid` · **PR**: #2387 Surfaces points earned/available during the checkout flow. Uses Shopify checkout extensions under the hood (separate `apps/shopify/extensions/checkout-points` extension). ## Points-for-spend nudge widgets **Files**: `extensions/widget/blocks/product-points-nudge.liquid` · **Base**: PR #1738 · **Placements**: PRs #2190 (FAB variant), #2163 (product page variant) Shows the points a shopper will earn on the current product/cart, with conversion mechanics tied to [points-for-spend](https://docs.shopify.com/discount-functions) via the configured multiplier. PR #1738 shipped the base implementation, PRs #2190 and #2163 added the FAB and product-page placements. ## Global widget toggle **PRs**: #2002, #2004 Merchants can disable the whole widget surface from the embedded admin's **On-site content** page (PR #2002), wired through to a programme-disable button (PR #2004). ## Widget settings scrolling + schema + export ordering * PR #1652 — widget settings form now scrolls on overflow. * PR #1845 — widget settings exports reordered for consistent theme editor field order. * PR #2431 — schema names shortened to stay under Shopify's 25-character limit. ## CSP allowances The Nudj user app sets a CSP `frame-ancestors` directive; PR #1638 added Shopify storefront domains so the widgets render without being blocked by the browser. ## Preview URL selection When the merchant previews a widget from the embedded admin: * PR #1637 picks the first domain for the widget's app URL. * PR #1896 provides a CSP + env override for non-production deployments. * PR #1899 prefers `customDomains` over the generic `userDomain` for accurate branded previews. ## Next steps Shopify-backed reward types that the loyalty widget exposes. Refer-a-Friend widget + landing URL configuration. > Last reviewed: 2026-04 · Related PRs: #1634, #1635, #1637, #1638, #1646, #1652, #1659, #1674, #1738, #1845, #1886, #1896, #1899, #1974, #1981, #2002, #2004, #2045, #2163, #2190, #2224, #2238, #2240, #2253, #2256, #2257, #2387, #2431 # Shopify Webhook Integration Source: https://docs.nudj.cx/enterprise/shopify/webhooks How Nudj registers Shopify webhooks, which topics we subscribe to for achievement triggers, and why some topics were disabled. Nudj subscribes to Shopify webhooks so merchants don't have to wire events manually. This page is the reference for which topics are consumed, how registration happens, and the hotfixes applied along the way. These Shopify topics drive Nudj's internal event pipeline, which in turn can trigger achievements, fire outbound Nudj webhooks, and update metrics. For the outbound Nudj webhook catalog and payload shapes, see [Webhook Events Catalog](/enterprise/webhooks-events-catalog). ## Programmatic registration PR #1684 replaced manual webhook registration with **programmatic registration** on app install. Nudj's install flow calls Shopify's webhook API to register every topic the app consumes, so merchants don't manage subscriptions themselves. ## Achievement-trigger topics PR #1780 added **16 new Shopify webhook topics** to drive achievement triggers. They span the core commerce lifecycle: | Category | Example topics | | - | - | | Orders | `orders/create`, `orders/paid`, `orders/fulfilled`, `orders/cancelled` | | Customers | `customers/create`, `customers/update` | | Carts | `carts/create`, `carts/update` | | Checkouts | `checkouts/create`, `checkouts/update` | | Products | `products/create` | | App lifecycle | `app/uninstalled`, `app/scopes_update` | Each incoming webhook resolves to the correct community via the Shopify event → community resolver (PR #1654), then flows into the same `handle-event.ts` pipeline that drives all Nudj achievements. ## Removed / unsupported topics Not every topic made it into production — Shopify occasionally deprecates scopes, and some topics were pulled to reduce background cost: * **Subscription contracts** — PR #1808 removed subscription contract webhooks and their scope after Shopify's subscription API shape changed. * **Unsupported topics** — PR #1789 removed webhook topics Shopify no longer accepts from apps, preventing install-time failures. * **Compliance webhooks config** — PR #2323 fixed an invalid `[compliance_webhooks]` TOML that was blocking the dev deployment pipeline. ## Cost-protective hotfixes Two production incidents drove hotfixes into the webhook layer: * **PR #2112** — hotfix disabling certain Shopify webhooks to stop a Redis cost spiral caused by high-cardinality event traffic. * **PR #2124** — return `410 Gone` on disabled webhook routes to force Shopify to unregister them. `410` is the Shopify-documented signal to remove a subscription; returning other codes would leave Shopify retrying indefinitely. These hotfixes are still in force for the affected topics. If your merchant integration depends on a specific topic, confirm it's in the active set before relying on it. ## Session Service PR #1833 implemented the **Shopify Sessions Service** — the backing store for merchant access tokens (encrypted per PR #1484). Webhooks arrive pre-authenticated; the Session Service resolves the token needed to make any outbound Shopify API call triggered by the webhook. ## Next steps Outbound Nudj webhook events that these Shopify topics ultimately drive. Where webhook registration fits in the full install flow. > Last reviewed: 2026-04 · Related PRs: #1484, #1654, #1684, #1780, #1789, #1808, #1833, #2112, #2124, #2323 # Enterprise SSO via OIDC Source: https://docs.nudj.cx/enterprise/sso-authentication Configure OpenID Connect single sign-on for your organisation. OIDC is the only SSO protocol Nudj supports. Nudj supports **OpenID Connect (OIDC)** as its single sign-on protocol. If your identity provider speaks OIDC — Okta, Auth0, Microsoft Entra ID, Google Workspace, Ping, Keycloak, or any conformant provider — you can wire it up from the admin panel in a few minutes. Nudj does **not** support SAML 2.0, LDAP, Active Directory Federation Services, or any protocol other than OIDC. If your organisation requires SAML, raise a request with your account manager — there is no self-serve path today. ## What OIDC SSO gives you * One set of credentials managed in your existing identity provider. * Server-to-server user lookup via the OIDC userinfo endpoint — no user password storage at Nudj. * A deterministic redirect URI per organisation so your security team can scope the allowed list precisely. For the full step-by-step setup, see [OAuth Integration Setup](/enterprise/oauth-authentication). This page summarises the enterprise-specific behaviour and recent setup polish. ## Self-serve OIDC configuration The Authentication tab of the admin panel (`Settings → Organisation → Authentication`) hosts the OIDC config. Two PRs in the last year materially changed the setup experience: **PR #1466**: Paste the issuer URL (e.g. `https://login.example.com`) and Nudj reads the `/.well-known/openid-configuration` document to populate the authorisation, token, and userinfo endpoints. The advertised scopes are surfaced so you can pick custom scopes beyond the OIDC defaults. **PR #1433**: The generated `oidcConfig.id` is displayed directly in the Authentication tab after you save. Previously admins had to email Nudj support for this value. Use it to build the redirect URI your provider will whitelist. Admin Login & Authentication settings with active providers ## Redirect URI The redirect URI template is: ``` https://${rootDomain}/api/auth/callback/organisation-oidc-${oidcConfig.id} ``` * `${rootDomain}` is the organisation's primary domain, shown under `Settings → Organisation → Domains`. * `${oidcConfig.id}` is the ID displayed in the Authentication tab after save. Paste the fully-resolved URL into your provider's allowed redirect list — providers match exactly on the string, including scheme and absence of trailing slash. ## Required scopes Nudj requests `openid`, `profile`, and `email` by default. These are the only mandatory scopes. You can opt in to: * `offline_access` — issues a refresh token so Nudj can extend a session without re-prompting. * Custom scopes — list any extra scopes your provider advertises (PR #1466 added this field). Nudj consumes the ID token and calls the userinfo endpoint for profile data. User identity is keyed on the `sub` claim; email is used for human-readable identification only. ## PKCE The OIDC flow enforces [PKCE](https://datatracker.ietf.org/doc/html/rfc7636) with an S256 code challenge. PR #1538 tightened the PKCE cookie validation on the callback. If you see `PKCE cookie missing` in the callback URL: * Confirm the callback is returning to the same origin that initiated auth. Embedded webviews that block third-party cookies will break the short-lived PKCE cookie round-trip. * Match the redirect URI's host to the host your users start the login flow from. ## What is explicitly not supported Be direct with your security team — these are not features of Nudj, regardless of how the provider markets integration: * **SAML / WS-Fed / LDAP** — no support. OIDC only. * **Just-in-time group-to-role mapping** — Nudj users are created on first successful login with the attributes from the ID token/userinfo response. There is no separate directory-sync process, no group-to-role mapping, and no SCIM endpoint. * **Nudj-hosted MFA** — MFA lives at your identity provider. Nudj does not store passwords or issue MFA challenges itself. * **Risk-based step-up auth / geofencing** — not implemented at Nudj; rely on your identity provider's policies. ## Common pitfalls The redirect URI at the provider doesn't match the one Nudj sends. Re-copy the URL from the Authentication tab — pay attention to the `oidcConfig.id`, which regenerates if you delete and re-create the config. Remove any custom scope the provider doesn't advertise. The auto-fetch flow will list the scopes your provider publishes. The provider didn't include `email` in the userinfo response, or the user's profile in the provider has no email set. Nudj creates the user but cannot email them — reconfigure the provider to issue the `email` claim in the ID token. ## Next steps Step-by-step provider configuration. Alternative flow for pre-authenticated users (JWT-based session handoff). > Last reviewed: 2026-04 · Related PRs: #1466, #1433, #1538, #618, #876, #878 # Universal Embed Widget Source: https://docs.nudj.cx/enterprise/universal-embed-widget The cross-platform website embed widget with an admin customisation panel, for any site that isn't running Shopify. PR #1971 shipped a **universal website embed widget** plus an admin-side customisation panel. This is the recommended embed path for any surface that isn't a Shopify storefront (CMS, custom sites, partner portals). Organisation branding and appearance settings ## What it is A small embed script you drop into your site. The script finds a placeholder `
` and injects a Nudj widget wired to your community. The widget is styled from the [community theme](/enterprise/white-labeling) and respects the embed configuration created in the admin panel. ## Setup **The page must be served over HTTPS.** The widget signs members in with cookies marked `Secure`, which browsers refuse to set on an `http://` page. On an insecure page the widget still renders, but sign-in silently fails — no error is shown. `http://localhost` counts as secure, so local development is unaffected. Load the page over `http://` and the browser console will warn you. Open the admin panel's embed customisation panel (PR #1971). Choose the widget layout, theme overrides, and the authentication mode — [OIDC SSO](/enterprise/sso-authentication) or [API Link](/enterprise/api-link-user-authentication) token handoff. The customisation panel surfaces a ready-to-paste snippet. It looks something like: ```html theme={null}
```
Paste the snippet anywhere on the page. The widget renders into the placeholder div on first paint. * **OIDC flow** — the user clicks "Sign in" inside the widget and is redirected through your identity provider and back. * **API Link flow** — your server issues a Nudj token and your page passes it to the widget (see [API Link](/enterprise/api-link-user-authentication) for the protocol).
## CSP allowlist PR #2333 opened the Nudj user app's CSP `frame-ancestors` directive so the universal embed can render on arbitrary third-party domains without the browser blocking it. This matters because the widget ships as an iframe under the hood — without the CSP change, every new embedding host would have needed a manual allowlist entry. ## When to pick the universal embed | Your surface | Best fit | | - | - | | Shopify storefront | [Shopify storefront widgets](/enterprise/shopify/storefront-widgets) — richer block catalog, theme editor integration | | Any other ecommerce / CMS / custom site | Universal embed widget (this page) | | Native mobile app | [Mobile webview](/enterprise/embedding-integration) | | You host the full user app on your domain | Iframe + CNAME ([white-labeling](/enterprise/white-labeling)) | ## Differences from the Shopify widget The universal embed and the Shopify theme app extension share the same underlying Nudj user app. What differs: * **Distribution** — the Shopify widget is a theme app extension merchants add from the theme editor; the universal embed is a script tag you host. * **Configuration surface** — Shopify widgets are configured per-block in the theme editor; the universal embed is configured once in the admin panel. * **CSP** — Shopify storefront domains are allowlisted by PR #1638; universal embed uses the wider allowance from PR #2333. ## Next steps Compare all four embedding modes. Theme the embedded widget to match your brand. > Last reviewed: 2026-04 · Related PRs: #1971, #2333, #1638 # Webhook Delivery Logs Source: https://docs.nudj.cx/enterprise/webhook-logs Inspect webhook delivery history, retry state, and failure reasons via the Admin API. Every webhook Nudj dispatches is tracked in the delivery log. Use the log to debug failed deliveries, confirm a specific event reached your endpoint, or audit integration health. Webhook configs live under `/api/v2/admin/webhooks/configs`. Delivery logs live under `/api/v2/admin/webhooks/logs` — the two endpoints are separate. See the [Admin API reference](/api-reference/admin/get-webhooks-logs) for the canonical request/response schema. ## What gets logged Every delivery attempt records: | Field | Meaning | | - | - | | `deliveryId` | Unique per delivery, stable across retries — matches `X-Nudj-Delivery-Id` | | `webhookConfigId` | Which configured endpoint this delivery targets | | `event` | Event type from the [catalog](/enterprise/webhooks-events-catalog) | | `url` | Target URL at time of delivery | | `status` | `pending`, `delivered`, `retrying`, `failed` | | `attempts` | Number of delivery attempts so far | | `lastAttemptAt` | Timestamp of the most recent attempt | | `nextAttemptAt` | Timestamp of the next scheduled attempt (if `retrying`) | | `responseStatus` | HTTP status code from the last response | | `responseBody` | Short response excerpt (for diagnostics) | | `error` | Error message if the request failed before getting a response | ## Listing deliveries Query the logs via the Admin API — see the [`GET /webhooks/logs`](/api-reference/admin/get-webhooks-logs) reference for the full filter surface. Typical filters: * By config (`webhookConfigId`) — only this endpoint's deliveries. * By event (`event`) — only this event type. * By status (`status`) — e.g. only `failed`. * By time window (`since`, `until`) — e.g. the last hour. ## Using the log to debug Take the action that should fire the webhook (complete a challenge, award points, etc.). Query `/webhooks/logs` filtered by `webhookConfigId` and ordered by `lastAttemptAt desc`. The newest entry is your attempt. * `delivered` — your endpoint returned 2xx. If your system didn't process it, the issue is on your side. * `retrying` — the endpoint returned a retryable error. Check `responseStatus` + `responseBody`. * `failed` — retries exhausted. Check `error` and fix the endpoint before retriggering. If your endpoint is returning 401 with "invalid signature", confirm you're HMAC-ing the **raw** body — re-serialised JSON produces a different byte sequence. See [Webhook Implementation](/enterprise/webhook-rewards#signature-verification) for a reference implementation. ## Retry policy recap * `5xx`, timeouts, and connection errors trigger a retry with exponential backoff. * `2xx` marks the delivery `delivered`. * `410 Gone` is the documented signal for permanent removal — Nudj honours this so your side can fence off a deprecated endpoint cleanly (PR #2124 shipped this behaviour on Shopify's disabled topics). * Other `4xx` responses are terminal and do not retry. ## Related pages The protocol — signing, retries, endpoint examples. Every event type Nudj emits. > Last reviewed: 2026-04 · Related API: `GET /api/v2/admin/webhooks/logs` # Webhook Implementation Source: https://docs.nudj.cx/enterprise/webhook-rewards Sign verification, retry semantics, and endpoint examples for Nudj webhooks. Nudj emits HTTP POST webhooks for domain events your downstream systems care about — reward earned, reward delivered, subscription updates, Shopify-driven achievement triggers, and more. This page covers the protocol — signatures, retries, and endpoint implementation. For the full list of emitted events, see the [events catalog](/enterprise/webhooks-events-catalog). To debug a delivery, see the [delivery logs](/enterprise/webhook-logs) page. ## Configuring a webhook Webhook configurations are managed via the Admin API under `/api/v2/admin/webhooks/configs`. Each config specifies: * **`url`** — your endpoint. Must be HTTPS in production. * **`events`** — list of event types the config subscribes to. See [Webhook Events Catalog](/enterprise/webhooks-events-catalog). * **`secret`** — used to compute the HMAC signature on each delivery. * **`active`** — pause or resume deliveries without deleting the config. Example config: ```json theme={null} { "url": "https://your-api.example.com/webhooks/nudj", "events": ["reward.earned", "reward.delivered", "reward.delivery_failed"], "secret": "whsec_...", "active": true } ``` ## Request shape Every delivery is an HTTP POST with a JSON body: ```json theme={null} { "event": "reward.earned", "timestamp": "2026-04-17T14:30:00.000Z", "data": { "reward": { "id": "reward_abc123", "type": "asset", "value": 100, "name": "Weekly Bonus" }, "user": { "id": "user_xyz789", "externalUserId": "customer_456" }, "challenge": { "id": "challenge_def456", "name": "Weekly Shopping Challenge" } } } ``` Headers: * `Content-Type: application/json` * `X-Nudj-Signature: sha256=` — HMAC-SHA256 of the raw body, signed with your configured `secret`. * `X-Nudj-Event` — convenience header mirroring `event` in the payload. * `X-Nudj-Delivery-Id` — unique per delivery attempt, stable across retries. Use this to idempotency-key your processing. ## Signature verification Compute HMAC-SHA256 over the **raw** request body using your configured `secret`, then compare against the value after `sha256=` in the `X-Nudj-Signature` header. Use a timing-safe comparison. ```javascript Node.js / Express theme={null} import express from "express"; import crypto from "crypto"; const app = express(); // Raw body is required for signature verification app.post( "/webhooks/nudj", express.raw({ type: "application/json" }), (req, res) => { const signature = (req.headers["x-nudj-signature"] || "").replace( "sha256=", "" ); const expected = crypto .createHmac("sha256", process.env.NUDJ_WEBHOOK_SECRET) .update(req.body) .digest("hex"); if ( signature.length !== expected.length || !crypto.timingSafeEqual( Buffer.from(signature, "hex"), Buffer.from(expected, "hex") ) ) { return res.status(401).send("invalid signature"); } const event = JSON.parse(req.body.toString("utf8")); // process event here, ideally async via a queue res.status(200).send("ok"); } ); ``` ```python Python / Flask theme={null} from flask import Flask, request, abort import hmac, hashlib, os app = Flask(__name__) @app.post("/webhooks/nudj") def handle_nudj_webhook(): raw = request.get_data() signature = request.headers.get("X-Nudj-Signature", "").removeprefix("sha256=") expected = hmac.new( os.environ["NUDJ_WEBHOOK_SECRET"].encode(), raw, hashlib.sha256, ).hexdigest() if not hmac.compare_digest(signature, expected): abort(401) # process JSON payload (request.get_json() is safe after signature passes) return ("ok", 200) ``` Verify the signature against the **raw** request body before parsing as JSON. Re-serialising the parsed body will produce a different byte sequence and the HMAC will never match. ## Response and retry semantics * Respond with `2xx` within 30 seconds to acknowledge receipt. * Any `5xx` response, connection failure, or timeout triggers a retry. * Retries use exponential backoff. * A delivery that fails every retry attempt is marked `failed` in the [delivery logs](/enterprise/webhook-logs). ### Idempotency Process events idempotently. The `X-Nudj-Delivery-Id` header is stable across retries of the same delivery attempt, and the payload's `data.*.id` fields identify the underlying entity. Storing processed delivery IDs in your side lets you no-op duplicates safely. ## Local development Use a tunnel such as [ngrok](https://ngrok.com) or [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) to expose your local endpoint: ```bash theme={null} ngrok http 3000 # paste the HTTPS forwarding URL into your webhook config's `url` ``` ## Related pages Reference of every event type Nudj emits, with example payloads. Inspect retry state, bodies, and failures via the Admin API. Higher-level integration patterns for getting rewards to downstream systems. > Last reviewed: 2026-04 # Webhook Events Catalog Source: https://docs.nudj.cx/enterprise/webhooks-events-catalog Reference of event types Nudj emits via outbound webhooks, grouped by domain. This page enumerates the outbound webhook events Nudj publishes to configured endpoints. For delivery mechanics (signing, retries, example endpoint code) see [Webhook Implementation](/enterprise/webhook-rewards). For delivery history and debugging, see [Webhook Delivery Logs](/enterprise/webhook-logs). Subscribe to specific events per webhook config — delivering every event to every endpoint burns your endpoint and inflates the delivery logs. The per-config `events` field filters the stream. ## Reward events The core events most integrations subscribe to. | Event | When it fires | Typical consumer | | - | - | - | | `reward.earned` | User completes a challenge or hits a qualification condition and a reward is credited | Loyalty wallet sync | | `reward.delivered` | Reward successfully delivered to the user or the downstream system | Confirmation email | | `reward.delivery_failed` | Delivery failed after retries | Alerting / support tooling | | `reward.redeemed` | A redeemable reward (discount code, checkout link, etc.) has been consumed | Inventory update | | `reward.expired` | Time-limited reward expired without redemption | Analytics | ## Engagement events | Event | When it fires | | - | - | | `challenge.started` | User joins / starts a challenge | | `challenge.completed` | User completes a challenge | | `action.completed` | User completes a single action (quiz, question, submission, etc.) | | `achievement.earned` | User hits an achievement (including tiered achievements) | | `points.awarded` | Points transaction applied (positive or negative) | ## Referral events The referral distribution events fire when a referee qualifies: | Event | When it fires | | - | - | | `referral.referrer_credited` | Referrer's reward distributed | | `referral.referee_credited` | Referee's welcome reward distributed | ## Shopify-driven events (inbound bridge) PR #1780 subscribed Nudj to **16 Shopify webhook topics** that drive internal events. These are consumed inside Nudj's `handle-event.ts` pipeline and typically trigger `achievement.earned` / `points.awarded` outbound webhooks rather than being re-emitted verbatim. See [Shopify Webhook Integration](/enterprise/shopify/webhooks) for the topic list. If a Shopify topic does pass through verbatim, the outbound event name is prefixed `shopify.` (e.g. `shopify.orders_create`) and the payload includes both the Nudj user context and the raw Shopify event body. ## Payload conventions Every payload is wrapped in a stable envelope: ```json theme={null} { "event": "reward.earned", "timestamp": "2026-04-17T14:30:00.000Z", "deliveryId": "del_2f3a...", "data": { // event-specific body } } ``` * `deliveryId` matches the `X-Nudj-Delivery-Id` header. Use it as your idempotency key. * Entity IDs inside `data.*` are stable across the lifecycle of the entity. * Timestamps are ISO-8601 UTC. ## Signing and retries Every event is signed with HMAC-SHA256 per [Webhook Implementation](/enterprise/webhook-rewards). Delivery retries with exponential backoff on `5xx`, timeouts, and connection errors. Terminal `4xx` responses (other than the documented `410 Gone` — see Shopify-specific webhooks page) are not retried. ## Next steps Sign verification, retries, sample endpoints. Inspect delivery history and retry state. > Last reviewed: 2026-04 · Related PRs: #1780 # Custom Domains and Theming Source: https://docs.nudj.cx/enterprise/white-labeling Serve Nudj from your own domain and match the visual identity of your brand via the community theme system. Nudj's white-label surface is pragmatic: a custom domain for your user-facing URLs and a theme system that matches the look of the embedded experience to your brand. There is no Nudj-supplied native mobile app published under a customer's developer account. If a conversation with your account manager suggests otherwise, raise it — the platform does not currently ship that product. ## Custom domains ### CNAME setup Point your chosen subdomain at the Nudj user app via a CNAME record: ```dns theme={null} engagement.yourcompany.com CNAME your-org.nudj.cx. ``` The admin panel's **Domains** tab tracks verification state; SSL certificates are issued automatically once the CNAME resolves. Full API reference: [Custom Domains](/api-reference/custom-domains). ### Preview link PR #2575 added a preview link derived from the organisation's white-label config — the Domains page in the admin panel surfaces the link directly so teams can sanity-check branded URLs before announcing a launch. ### Multiple domains per organisation An organisation can have multiple verified domains — used for regional sites, white-labelled partners, or staging. The Shopify widget preview logic (PR #1899) prefers `customDomains` over the generic `userDomain` so in-merchant admin previews render against the branded URL. ## Theming ### Source of truth Theme variables live on the `community.theme` object. The admin panel's **Branding & Theming** section is the canonical editor. Theme values cascade from the organisation's default community down to child communities; child communities can override individual variables. Reference: [Widget Theming System](https://github.com/nudj-digital/nudj/blob/main/docs/widget-theming-system.md) in the engineering repo has the full variable list, data flow, and CSS variable mapping. ### Theme presets PR #1886 added theme presets — pre-built configurations that ship values for colours, typography, spacing, and component shape. Admins can pick a preset in the Shopify embedded admin and tune individual variables without starting from scratch. ### Where theming applies | Surface | Theme source | | - | - | | Nudj user app on your domain | `community.theme` resolved at page render | | Shopify storefront widgets | `community.theme` resolved from the widget block config (PR #1646) | | Universal embed widget | `community.theme` passed through the embed script | | Email templates | Organisation brand colours + logo | ## What's in scope * Custom domain with automatic SSL. * Logo, brand colours, typography, component shape via the theme system. * Email sender name + reply-to. * Community-scoped theme overrides for multi-brand organisations. ## What's explicitly not supported * Native iOS / Android apps published under your Apple Developer or Google Play account. * Biometric device unlock for Nudj sessions. * Push notifications sent from a branded mobile app. * Offline mode or geofenced content gating. * CDN-level edge customisation beyond what Vercel provides to the Nudj user app. ## Next steps API reference for programmatic domain management. If you embed rather than redirect, the theme flows through the embed script. > Last reviewed: 2026-04 · Related PRs: #2575, #1886, #1646, #1899 # Glossary Source: https://docs.nudj.cx/getting-started/glossary Complete dictionary of Nudj platform terminology Quick reference for all platform-specific terms and concepts. ## A **Achievement** : A milestone or accomplishment that users earn through specific actions or reaching certain thresholds. Achievements often come with badges, titles, or special rewards. **Action** : An individual task or activity within a challenge. Examples include answering a quiz, sharing content, uploading a photo, or watching a video. **Action Participation** : A record of a user completing a specific action within a challenge. **Admin** : A user role with permissions to create and manage communities, challenges, and rewards. **Analytics** : Data and insights about user engagement, challenge performance, and community health. **API** : Application Programming Interface - allows developers to integrate Nudj features into other systems. **Regular Reward** : A guaranteed reward that users can redeem using points. Regular rewards have defined quantities and availability. ## B **Badge** : A visual achievement indicator displayed on user profiles to showcase accomplishments. **Bonus Points** : Additional points awarded for special circumstances like streak maintenance or speed bonuses. ## C **Campaign** : A coordinated set of challenges and rewards designed to achieve specific engagement goals. **Challenge** : A collection of one or more actions that users complete to earn rewards. Also known as a quest. **Challenge Group** : A collection of related challenges organized together for easier navigation. **Community** : A branded space where users engage with content, challenges, and each other around shared interests. **Community Manager** : An admin responsible for day-to-day community engagement and content creation. ## D **Dashboard** : The administrative interface for managing communities, viewing analytics, and creating content. **Distribution** : The process of awarding rewards to users, either manually or automatically. **Draw Date** : The scheduled date when winners are selected for entry-based rewards. ## E **Engagement** : User interaction with platform features including challenge completion, social features, and content consumption. **Entry (Reward Entry)** : A chance to win in a prize draw or giveaway. Users can accumulate multiple entries. **Experience Points (XP)** : Non-spendable points that accumulate to show user status and determine leaderboard position. ## F **Feature Flag** : A toggle that enables or disables specific platform features for testing or gradual rollout. **Feed** : The main content stream showing recent challenges, community updates, and social activity. ## G **Gamification** : The application of game-design elements (points, levels, competition) to drive engagement. **Giveaway** : A reward distribution method where winners are randomly selected from entry holders. ## I **Integration** : Connection between Nudj and external systems or platforms. ## L **Leaderboard** : A ranking system showing top performers based on XP or other metrics. **Level** : A tier or rank achieved by accumulating XP or completing specific requirements. **Limited Time Challenge** : A challenge only available for a specific duration. ## M **Member** : A user who has joined a specific community. **Mission** : A long-term objective composed of multiple challenges or achievements. **Multi-Action Challenge** : A challenge requiring completion of multiple different actions. ## O **OAuth** : Authorization framework for delegated access; authentication for login flows is typically provided by OpenID Connect (built on OAuth 2.0). **Organisation** : The brand or company that owns and manages communities on Nudj. ## P **Platform Config** : System-wide settings that affect the entire Nudj installation. **Points** : Spendable currency earned by completing challenges and used to redeem rewards. **Poll** : An action type where users vote or express preferences. **Post** : Content created by admins or users within a community. **Prize Draw** : A lottery-style reward where winners are randomly selected. **Profile** : A user's personal page showing achievements, statistics, and activity. ## Q **Quest** : Another term for challenge, emphasizing the adventure/gaming aspect. **Quiz** : An action type where users answer questions to test knowledge. ## R **Recurring Challenge** : A challenge that resets periodically (daily, weekly, monthly). **Redemption** : The process of exchanging points for regular rewards. **Reward** : An incentive users earn through participation, either as an asset or entry. **Reward Inventory** : The available stock of regular rewards. ## S **Social Share** : An action type requiring users to share content on social media platforms. **Streak** : Consecutive days or instances of participation, often with bonus rewards. **Super Admin** : The highest admin level with full platform configuration access. ## T **Tag** : A label used to categorize and organize content, challenges, or users. **Team** : Admin users who collaborate to manage a community or organization. **Template** : Pre-built challenge, reward, or community configurations for quick setup. **Theme** : Visual customization including colors, fonts, and branding for communities. **Third-Party Config** : Settings for external service integrations. **Tier** : A level within a progression system, often unlocking new privileges. **Token** : Authentication credential for API access. ## U **Unlock** : Making a challenge or reward available after meeting certain requirements. **User** : Any person with an account on the platform. **User-Generated Content (UGC)** : Content created by community members rather than admins. ## V **Variable** : Dynamic content that changes based on user or context (e.g., ). **Verification** : Process of confirming action completion or user identity. ## W **Webhook** : Automated notification sent to external systems when events occur. **Widget** : Embeddable Nudj components for external websites. **Winner** : User selected to receive an entry-based reward. ## X **XP (Experience Points)** : Cumulative points showing user progress and status that cannot be spent. ## Common Abbreviations | Abbreviation | Full Term | | - | - | | **API** | Application Programming Interface | | **CMS** | Content Management System | | **CRM** | Customer Relationship Management | | **KPI** | Key Performance Indicator | | **OAuth** | OAuth (authorization framework for token-based access) | | **ROI** | Return on Investment | | **SSO** | Single Sign-On | | **UGC** | User-Generated Content | | **UI** | User Interface | | **UX** | User Experience | | **XP** | Experience Points | ## Related Resources Understand core concepts in detail Technical terminology and endpoints Can't find a term? Contact support at [support@nudj.cx](mailto:support@nudj.cx) and we'll add it to the glossary. # Platform Basics Source: https://docs.nudj.cx/getting-started/platform-basics Essential concepts and terminology for understanding the Nudj platform Understanding these core concepts will help you navigate and utilize the Nudj platform effectively. Think of this as your gamification vocabulary lesson (but way more fun than actual vocabulary lessons). As Aristotle said, "We are what we repeatedly do. Excellence, then, is not an act, but a habit"—and understanding these concepts is the habit that builds platform expertise. ## Core Concepts ## Communities **What they are**: Branded spaces where users gather around shared interests and your brand **Key features**: * Custom branding and themes * Member management * Content organization * Social features ## Challenges **What they are**: Interactive quests that guide users through engaging activities **Types**: * **Single Action**: Simple one-step challenges * **Multi-Action**: Complex challenges with multiple steps * **Recurring**: Daily/weekly challenges that reset * **Limited Time**: Time-bound special events ## Actions **What they are**: Individual tasks within challenges that users complete **Common types**: * **Quiz**: Answer questions to test knowledge * **Poll**: Share opinions and preferences * **Social Share**: Amplify content on social media * **Content View**: Watch videos or read articles * **Photo Upload**: Share user-generated content ## Rewards **What they are**: Incentives users earn through participation **Two types**: * **Regular Rewards**: Guaranteed rewards users can redeem with points * **Entries**: Chances to win in prize draws or giveaways ## Currency System Nudj uses a dual currency system because apparently one type of virtual money isn't confusing enough: **Spendable Currency** (The fun one) * Earned through challenge completion * Used to redeem actual rewards * Can be transferred or gifted (sharing is caring) * Balance decreases when spent (just like real money, sadly) **Status Currency** (The bragging rights one) * Earned through all platform activities * Never decreases (your ego protection plan) * Determines leaderboard position * Unlocks achievement levels ## User Journey The typical user progression through the Nudj platform: ```mermaid theme={null} graph LR A[Join Community] --> B[Browse Challenges] B --> C[Complete Actions] C --> D[Earn Points/XP] D --> E[Redeem Rewards] E --> F[Climb Leaderboard] F --> B ``` ## Engagement Mechanics ## Leaderboards Dynamic rankings that tap into everyone's inner competitor: * **Global**: Compare with all community members (good luck beating Bob from accounting) * **Weekly/Monthly**: Time-based competitions (fresh chances to show off) * **Friend-based**: Compete with connections (friendship destroyer mode) ## Achievements Milestone-based recognition for your digital accomplishments: * **Badges**: Visual trophies you can actually show off * **Titles**: Special designations for top performers * **Unlocks**: Access to exclusive content (VIP treatment, basically) ## Streaks Consistency rewards for showing up (revolutionary concept): * Daily login streaks * Challenge completion streaks * Engagement streaks ## Platform Roles * Joins communities * Completes challenges * Earns rewards * Engages socially * Creates communities * Designs challenges * Manages rewards * Views analytics * Platform configuration * Multi-community management * Advanced analytics * User management * API integration * Custom features * Automation setup * Data access ## Key Terminology | Term | Definition | | - | - | | **Community** | A branded space for user engagement | | **Challenge** | A collection of actions users complete | | **Action** | A single task within a challenge | | **Quest** | Another term for challenge | | **Regular Reward** | A redeemable item with guaranteed availability | | **Reward Entry** | A chance to win in a prize draw | | **Points** | Spendable currency earned through participation | | **XP** | Experience points that show status and progress | | **Leaderboard** | Ranking system showing top performers | | **Achievement** | Recognition for reaching specific milestones | | **Streak** | Consecutive days/times of participation | ## Platform Navigation ## For Users 1. **Home/Feed**: See latest challenges and community updates 2. **Challenges**: Browse and participate in available challenges 3. **Rewards**: View and redeem available rewards 4. **Leaderboard**: Check rankings and competition 5. **Profile**: Manage settings and view achievements ## For Admins 1. **Dashboard**: Overview of community performance 2. **Community Settings**: Configure branding and features 3. **Challenge Manager**: Create and manage challenges 4. **Reward Center**: Set up and distribute rewards 5. **Analytics**: Track engagement and performance ## Best Practices * Check daily for new challenges * Maintain streaks for bonus rewards * Engage with community features * Complete multi-action challenges for bigger rewards * Track progress on leaderboards * Create varied challenge types * Balance difficulty and rewards * Maintain regular content updates * Monitor analytics for optimization * Engage with community feedback ## Next Steps Now that you understand the basics: Dive deeper into specific features in our [User Guide](/user-guide/introduction) Learn how to [complete your first challenge](/user-guide/challenges) Set up your first [community](/admin-guide/community-setup) and [challenges](/admin-guide/challenges/challenge-creation) # What is Nudj? Source: https://docs.nudj.cx/getting-started/what-is-nudj Discover how Nudj transforms customer engagement through gamification and community building Nudj is a **gamified engagement platform** that turns customers into advocates through challenges, rewards, and community. In the words of Mark Twain, "The man who does not read has no advantage over the man who cannot read"—and the brand that doesn't engage users has no advantage over those that do. Transform passive customers into active brand advocates through gamification ## The Nudj Experience A simple engagement loop: Join [brand communities](/user-guide/communities) aligned with your interests Complete [challenges](/user-guide/challenges/participation) with actions like quizzes, polls, and shares Earn Points & XP to climb [leaderboards](/user-guide/leaderboards) and unlock [rewards](/user-guide/rewards) Redeem points for [rewards](/user-guide/rewards) or enter prize draws ## Why Brands Choose Nudj ## Behavior-Based Engagement Create meaningful, behavior‑driven interactions that motivate real participation. ## Proven Gamification Framework Game mechanics that drive engagement: * **Challenges & Quests** - Structured activities that guide user journeys * **Points & Progression** - Clear advancement paths that motivate continued participation * **Leaderboards** - Social competition that drives repeat engagement * **Achievements** - Recognition systems that celebrate milestones ## Community-First Approach Build exclusive communities where members can: * Connect with like-minded individuals * Share experiences and content * Participate in brand conversations * Access exclusive opportunities ## Key Platform Components Branded spaces where users gather around shared interests Interactive quests that drive specific behaviors and engagement Granular tasks within challenges (quizzes, polls, social shares) Incentives ranging from digital assets to exclusive experiences ## For Brands * **Marketing Teams**: Run engagement campaigns and drive UGC * **Community Managers**: Build and nurture brand communities * **Loyalty Programs**: Gamify existing programs * **Customer Success**: Boost retention and LTV ## For Users * **Brand Enthusiasts**: Engage deeper with favorite brands * **Reward Seekers**: Earn perks and prizes * **Community Members**: Connect with others * **Achievement Hunters**: Complete challenges and climb leaderboards ## Platform Capabilities * Interactive challenge creation * Multi-action quest builder * Social sharing integration * User-generated content campaigns * Real-time participation tracking * Points & XP systems * Dynamic leaderboards * Achievement badges * Progression levels * Streak tracking * Digital reward distribution * Prize draw management * Inventory tracking * Automated fulfillment * Entry-based competitions * Engagement metrics * User behavior tracking * Challenge performance * Community health scores * ROI measurement ## The Nudj Difference **Beyond loyalty**: Nudj rewards engagement across social, content, and community—not just purchases. ## Traditional Engagement vs. Nudj | Traditional | Nudj Platform | | - | - | | Transaction-focused | Behavior-focused | | Points for purchases | Points for engagement | | Static rewards catalog | Dynamic challenges & rewards | | Individual actions | Community experiences | | Periodic engagement | Continuous interaction | ## Getting Started Get started: Learn how to set up and manage your Nudj community Discover how to participate and earn rewards Make your first API call Launch your first community without code Integrate Nudj into your systems ## Next Steps See [platform basics](/getting-started/platform-basics) Start with [community setup](/admin-guide/community-setup) Build your first [challenge](/admin-guide/challenges/challenge-creation) **Questions?** Check our [FAQs](/user-guide/faqs) or contact support at [support@nudj.cx](mailto:support@nudj.cx) # Introduction Source: https://docs.nudj.cx/introduction The OS for gamified community engagement # Welcome to Nudj Nudj is a B2B SaaS platform that turns passive audiences into active, thriving communities. We give you the tools to create branded spaces where your users complete challenges, earn rewards, and compete on leaderboards—all while building a deeper connection with your brand.