Skip to content

🐛 server: require identity-validation completion for ramp tracking#812

Open
mainqueg wants to merge 1 commit intomainfrom
onboarding
Open

🐛 server: require identity-validation completion for ramp tracking#812
mainqueg wants to merge 1 commit intomainfrom
onboarding

Conversation

@mainqueg
Copy link
Contributor

@mainqueg mainqueg commented Feb 19, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Ramp account tracking now only triggers when a user is active and identity validation is completed, preventing premature notifications.
  • Tests

    • Expanded test coverage to validate both tracking and multiple non-tracking onboarding scenarios.
  • Chores

    • Added a patch-level changeset and made the onboarding task schema publicly available for reuse.

Open with Devin

Greptile Summary

This PR correctly fixes the premature ramp-account tracking bug by adding three guard conditions to the USER_ONBOARDING_UPDATE handler:

  • user.status === "ACTIVE"
  • updatedTasks includes "IDENTITY_VALIDATION"
  • onboarding.IDENTITY_VALIDATION.status === "COMPLETED"

The changes are well-tested. Four new test cases cover every tracking scenario:

  1. ✅ Tracking fires when all three conditions are met (user active, IDENTITY_VALIDATION in updatedTasks, status COMPLETED)
  2. ✅ No tracking when updatedTasks doesn't include IDENTITY_VALIDATION
  3. ✅ No tracking when IDENTITY_VALIDATION status is not COMPLETED
  4. ✅ No tracking when user status is not ACTIVE

The test at line 547 explicitly validates that tracking is suppressed when IDENTITY_VALIDATION is absent from the onboarding object, confirming the optional chaining works correctly.

UserOnboardingTasks is now exported from server/utils/ramps/manteca.ts to allow direct schema reuse in the hook layer.

Confidence Score: 5/5

  • Safe to merge. The logic fix is correct with comprehensive test coverage, and all three guard conditions are properly implemented.
  • The PR implements the correct fix with proper guard conditions (user active, IDENTITY_VALIDATION in updatedTasks, status COMPLETED). Test coverage is comprehensive, including all four scenarios: tracking enabled, and no-tracking for each individual guard condition failure. The code correctly uses optional chaining (?.) to handle missing IDENTITY_VALIDATION safely. No functional issues identified.
  • No files require special attention.

Last reviewed commit: 3c1be87

@changeset-bot
Copy link

changeset-bot bot commented Feb 19, 2026

🦋 Changeset detected

Latest commit: 3c1be87

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@exactly/server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Require identity-validation completion on USER_ONBOARDING_UPDATE before triggering RampAccount tracking; export UserOnboardingTasks schema; update tests to cover positive and negative onboarding scenarios.

Changes

Cohort / File(s) Summary
Versioning
\.changeset/ready-groups-beam.md
Adds a patch-level changeset for @exactly/server with note: require identity-validation completion for ramp tracking.
Hook implementation & types
server/hooks/manteca.ts, server/utils/ramps/manteca.ts
Tightens USER_ONBOARDING_UPDATE handling to require user.status === "ACTIVE", updatedTasks includes IDENTITY_VALIDATION, and user.onboarding.IDENTITY_VALIDATION.status === "COMPLETED" before invoking RampAccount tracking. Exports UserOnboardingTasks schema.
Tests
server/test/hooks/manteca.test.ts
Updates and expands tests: adds negative cases (missing task, non-COMPLETED status, non-ACTIVE user) and a positive case requiring ACTIVE user with completed identity validation; adjusts payloads and assertions.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ServerHook as Manteca Hook
    participant Ramp as RampAccount
    participant Segment
    Client->>ServerHook: USER_ONBOARDING_UPDATE event (user, updatedTasks)
    ServerHook->>ServerHook: verify user.status == "ACTIVE" && updatedTasks contains IDENTITY_VALIDATION && user.onboarding.IDENTITY_VALIDATION.status == "COMPLETED"
    alt conditions met
        ServerHook->>Ramp: RampAccount.track(provider: "manteca", source: null, user)
        ServerHook->>Segment: segment.track(...) 
    else conditions not met
        ServerHook-->>Client: no tracking
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • nfmelendez
  • cruzdanilo
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: requiring identity-validation completion as a prerequisite for ramp tracking, which is implemented across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch onboarding

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link

Summary of Changes

Hello @mainqueg, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refines the server's webhook processing for user onboarding updates, specifically addressing an issue where RampAccount events might be tracked prematurely. The changes ensure that ramp tracking is contingent upon the explicit completion of identity validation, providing more accurate and compliant user state management. This prevents erroneous tracking and aligns the system with necessary regulatory steps.

Highlights

  • Enhanced Ramp Tracking Logic: Modified the USER_ONBOARDING_UPDATE webhook handler to ensure RampAccount events are only tracked when a user's IDENTITY_VALIDATION task is explicitly COMPLETED, in addition to their status being ACTIVE.
  • Schema Update: Incorporated the UserOnboardingTasks schema into the webhook payload validation to correctly parse onboarding task statuses.
  • Improved Test Coverage: Added comprehensive test cases to verify that RampAccount events are not tracked under various conditions where identity validation is incomplete, not the updated task, or the user is not active.
  • Schema Export: Exported the UserOnboardingTasks schema for broader use within the server.
Changelog
  • .changeset/ready-groups-beam.md
    • Added a new changeset file for a patch release.
  • server/hooks/manteca.ts
    • Imported UserOnboardingTasks for use in the webhook handler.
    • Updated the USER_ONBOARDING_UPDATE payload schema to include onboarding details.
    • Modified the condition for tracking RampAccount events to require IDENTITY_VALIDATION completion.
  • server/test/hooks/manteca.test.ts
    • Refactored the existing RampAccount tracking test to include onboarding data.
    • Added new test cases to verify that RampAccount is not tracked if identity validation is not the updated task, is not completed, or if the user is not active.
  • server/utils/ramps/manteca.ts
    • Exported the UserOnboardingTasks schema.
Activity
  • No activity has been recorded for this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

gemini-code-assist[bot]

This comment was marked as resolved.

@sentry
Copy link

sentry bot commented Feb 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.82%. Comparing base (1fc4d56) to head (3c1be87).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #812      +/-   ##
==========================================
- Coverage   70.52%   69.82%   -0.71%     
==========================================
  Files         212      212              
  Lines        8391     7990     -401     
  Branches     2770     2603     -167     
==========================================
- Hits         5918     5579     -339     
+ Misses       2227     2157      -70     
- Partials      246      254       +8     
Flag Coverage Δ
e2e 69.82% <100.00%> (+0.83%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/test/hooks/manteca.test.ts (1)

451-480: 🧹 Nitpick | 🔵 Trivial

Missing assertion for sendPushNotification in the positive case.

The handler calls sendPushNotification whenever the tracking condition fires, but the test only asserts on segment.track. A regression that removes or changes the push notification call would go undetected.

🧪 Suggested addition
+import * as onesignal from "../../utils/onesignal";
 ...
 it("tracks RampAccount when user is active and identity validation is completed", async () => {
   vi.spyOn(segment, "track").mockReturnValue();
+  vi.spyOn(onesignal, "sendPushNotification").mockResolvedValue(undefined);
   ...
   expect(segment.track).toHaveBeenCalledWith({...});
+  expect(onesignal.sendPushNotification).toHaveBeenCalledWith({
+    userId: account,
+    headings: { en: "Fiat onramp activated" },
+    contents: { en: "Your fiat onramp account has been activated" },
+  });
 });

@mainqueg mainqueg force-pushed the onboarding branch 2 times, most recently from fc8a58f to 3ff94c8 Compare February 23, 2026 18:39
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/test/hooks/manteca.test.ts (1)

451-480: 🧹 Nitpick | 🔵 Trivial

Consider asserting sendPushNotification was called in the positive-path test.

The positive path calls sendPushNotification (lines 233–237 in server/hooks/manteca.ts), but the test only asserts on segment.track. Since onesignal is already mocked at module level, adding a spy on sendPushNotification would lock in the full contract of the activation event.

🔍 Proposed addition
 import * as segment from "../../utils/segment";
+import * as onesignal from "../../utils/onesignal";

 it("tracks RampAccount when user is active and identity validation is completed", async () => {
   vi.spyOn(segment, "track").mockReturnValue();
+  vi.spyOn(onesignal, "sendPushNotification").mockResolvedValue(undefined);
   // ... rest of test ...
+  expect(onesignal.sendPushNotification).toHaveBeenCalledWith(
+    expect.objectContaining({ headings: { en: "Fiat onramp activated" } }),
+  );
 });

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c8911f and 3ff94c8.

📒 Files selected for processing (4)
  • .changeset/ready-groups-beam.md
  • server/hooks/manteca.ts
  • server/test/hooks/manteca.test.ts
  • server/utils/ramps/manteca.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
server/test/hooks/manteca.test.ts (2)

451-480: 🧹 Nitpick | 🔵 Trivial

LGTM — consider asserting sendPushNotification was also called in the positive path.

track() and sendPushNotification() both live inside the same if block in the hook. The test verifies track was called, but sendPushNotification (mocked via ../mocks/onesignal) is unasserted. Adding it would provide complete coverage of the successful activation flow.


509-561: 🧹 Nitpick | 🔵 Trivial

Consider adding a test for IDENTITY_VALIDATION entirely absent from the onboarding object.

The current negative tests cover IN_PROGRESS status and non-ACTIVE user status, but not the case where user.onboarding.IDENTITY_VALIDATION is undefined (the key is absent). Since OnboardingTaskInfo is optional(), this is a valid real-world payload shape. The ?.status === "COMPLETED" guard handles it correctly, but an explicit test would pin that behaviour.

✅ Suggested additional test
+    it("does not track when identity validation task is absent from onboarding", async () => {
+      vi.spyOn(segment, "track").mockReturnValue();
+      const payload = {
+        event: "USER_ONBOARDING_UPDATE",
+        data: {
+          updatedTasks: ["IDENTITY_VALIDATION"],
+          user: {
+            email: "test@example.com",
+            id: "user123",
+            numberId: "456",
+            externalId: userExternalId,
+            exchange: "ARGENTINA",
+            status: "ACTIVE",
+            onboarding: {},
+          },
+        },
+      };
+      const response = await appClient.index.$post({
+        header: { "md-webhook-signature": createSignature(payload) },
+        json: payload as never,
+      });
+
+      expect(response.status).toBe(200);
+      await expect(response.json()).resolves.toStrictEqual({ code: "ok" });
+      expect(segment.track).not.toHaveBeenCalled();
+    });
server/hooks/manteca.ts (1)

223-238: ⚠️ Potential issue | 🟠 Major

Add guard to verify all conditions before tracking or query Manteca for current state.

Manteca's USER_ONBOARDING_UPDATE webhook is not guaranteed to be atomic—the public integration guidance treats it as eventually consistent and recommends querying Manteca for the current onboarding state rather than assuming a single event contains all related updates. If the user status transition and identity validation completion arrive in separate events (e.g., IDENTITY_VALIDATION → COMPLETED first, then user → ACTIVE in a follow-up), the RampAccount tracking will silently never fire because the triple-AND guard will fail on the second event.

Either query Manteca's current user state when the webhook arrives to re-verify all three conditions, or implement idempotent tracking that handles split events correctly.


ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ff94c8 and b9fad69.

📒 Files selected for processing (4)
  • .changeset/ready-groups-beam.md
  • server/hooks/manteca.ts
  • server/test/hooks/manteca.test.ts
  • server/utils/ramps/manteca.ts

@cruzdanilo cruzdanilo marked this pull request as ready for review February 23, 2026 21:22
devin-ai-integration[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b5b7021622

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

externalId: string(),
exchange: string(),
status: picklist(UserStatus),
onboarding: UserOnboardingTasks,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep onboarding snapshot optional in webhook payload schema

Requiring user.onboarding at validation time causes partial USER_ONBOARDING_UPDATE webhook payloads (that include updatedTasks and user metadata but no full onboarding object) to fail early as bad manteca, so the handler never reaches the activation tracking/push logic for those events. Fresh evidence in this commit is that onboarding tests were rewritten to add user.onboarding everywhere, indicating this shape became mandatory only due to the new validator constraint.

Useful? React with 👍 / 👎.

sentry[bot]

This comment was marked as resolved.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

expect(response.status).toBe(200);
await expect(response.json()).resolves.toStrictEqual({ code: "ok" });
expect(segment.track).not.toHaveBeenCalled();
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing edge-case test: the current tests all populate onboarding.IDENTITY_VALIDATION, but there's no test for when this key is absent from the onboarding object (which is valid per the OnboardingTaskInfo = optional(...) schema). In that case payload.data.user.onboarding.IDENTITY_VALIDATION?.status === "COMPLETED" evaluates to false, so no tracking fires — which is correct. Adding a test explicitly documents this contract and prevents regressions if the optional chaining is ever refactored:

Suggested change
});
it("does not track when identity validation task is absent from onboarding", async () => {
vi.spyOn(segment, "track").mockReturnValue();
const payload = {
event: "USER_ONBOARDING_UPDATE",
data: {
updatedTasks: ["IDENTITY_VALIDATION"],
user: {
email: "test@example.com",
id: "user123",
numberId: "456",
externalId: userExternalId,
exchange: "ARGENTINA",
status: "ACTIVE",
onboarding: {}, // IDENTITY_VALIDATION absent
},
},
};
const response = await appClient.index.$post({
header: { "md-webhook-signature": createSignature(payload) },
json: payload as never,
});
expect(response.status).toBe(200);
await expect(response.json()).resolves.toStrictEqual({ code: "ok" });
expect(segment.track).not.toHaveBeenCalled();
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant