Skip to content

About

An simple example to show to customers how our manage product can be used with an AI agent.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

20 Commits

Folders and files

Repository files navigation

Subscription Assistant: Atomic + an AI agent

A deliberately small example of how an AI agent can use the Atomic API and Transact SDK to help a user find and cancel their subscriptions.

The user chats with an assistant in a mobile app. The agent looks merchants up through the Atomic API. When the user is ready to cancel, the agent puts a button in the app that opens Atomic's Transact SDK, where the user finishes the cancellation. Atomic's webhooks and the SDK's events go back to the agent, so it can tell the user how things went without being asked.

It's meant to be read, not deployed. There's no login, state lives in memory or a local SQLite file, and webhooks aren't verified. Each of those is a place where a real integration would do more.

Tech stack

Part What it uses
Agent LangChain createAgent with Claude (claude-opus-5-5) through @langchain/anthropic. Conversation memory is LangGraph's in-memory MemorySaver.
Server Node.js 22+ and Express 5, a WebSocket server (ws), and SQLite through Node's built-in node:sqlite for webhook events.
Atomic The Atomic API, called from the server with your API key and secret, and the Transact SDK, opened in the app with a public token.
Mobile app Expo (SDK 57) / React Native with @atomicfi/transact-react-native. It needs a development build because the Transact SDK has native code, so Expo Go won't run it.

Architecture

Everything the agent responds to goes through one queue. Three things write to it: the user, Atomic's webhooks, and the Transact SDK's events. The agent takes items off the queue one at a time for each user and sends each reply to the app over a WebSocket.

Architecture: the frontend sends user messages and Transact SDK events to a message queue on the backend, Atomic sends webhook events to the same queue, the agent takes one message at a time, calls the Atomic API through its tools, and sends replies back to the frontend, which launches Transact.
  • The user's messages go to POST /user-message, and the server queues them.
  • Transact SDK events: when the user leaves the Atomic screen, the app sends the SDK's onFinish or onClose payload to POST /transact-events. The server queues it as a system event, so the agent can check on the task and follow up.
  • Atomic webhooks arrive at POST /webhooks/atomic/manage-agent. task-status-updated events are saved to SQLite, and when a task finishes, the server also queues it. A cancellation is followed by a refresh task that resyncs the user's data; once the refresh completes, the agent reads the account to confirm the cancellation.
  • The agent sees system events as <system_event type="…"> messages the user never sees. Its system prompt says how to respond to each type.
  • Replies go to the app over the WebSocket, along with busy (for the typing indicator) and error events. Each user has one thread, keyed by their user ID.

When the agent wants the app to open Transact, a tool returns a launch_transact client action alongside its result. It travels with the agent's reply, and the app shows it as a button. The model only learns that the button is ready; it never sees the launch configuration.

Where things live

agent/          The agent: model, system prompt, tools
  tools/        One file per tool
atomic/         A small client for the Atomic API
server/
  index.js      Express app and WebSocket server
  queue.js      Per-thread message queue; runs the agent's turns
  bus.js        Sends events to the WebSockets listening on a thread
  db.js         SQLite: the test user and stored webhook events
  routes/       /user-message, /transact-events, /webhooks/atomic/manage-agent
mobile/         Expo app: chat screen, API client, Transact launcher

Tools

The agent has seven tools. Each runs on the server, so Atomic credentials never reach the model or the app.

Tool What it does Atomic API
upsert_user Makes sure Atomic knows the user, by creating them if needed. Called at the start of each conversation. POST /access-token
find_company Searches for a company by name and says whether Atomic can cancel subscriptions there. POST /company/search
list_cancellable_companies Lists every company where Atomic supports cancelling a plan. GET /company/list?actions=cancel-plan
get_user_subscriptions Shows the user's connected accounts, with the bills and plans Atomic found on each. GET /pay-link/accounts
get_actions_for_company Gets what Atomic can do for this user at a merchant: cancel-plan for a specific subscription, or connect-account if the user needs to sign in first. GET /pay-link/actions-for-company/:companyId
get_action_config Creates a public token and gives the app a "Cancel <company>" or "Connect <company>" button that opens Transact on a cancel-plan or connect-account action. Returns the settings the app needs to launch Transact on the right action. POST /access-token
check_events Reads the latest task statuses from the stored webhooks, to tell the user whether a cancellation went through. None. It reads the local database.

Running it locally

The project uses Atomic's sandbox by default. .env.example points at https://sandbox-api.atomicfi.com, and the Transact SDK uses the same environment.

What you need

  • Node.js 22.13 or later, with npm
  • macOS with Xcode and an iOS simulator running iOS 16.4 or later, plus CocoaPods, to build the app. It has been built with Xcode 27.0 and CocoaPods 1.16.2. The app is iOS only: there's an android script, but it hasn't been built or tested for Android. It needs a native build because the Transact SDK has native code, so Expo Go won't run it.
  • Ports 3000 and 8081 free, for the server and Metro. If another dev server is on port 3000, the app's requests reach it instead and fail with a 404. To use a different port, start the server with PORT=3001 npm run dev and set EXPO_PUBLIC_API_URL in mobile/.env to match, then restart Metro with npx expo start -c so the app picks it up.
  • An Atomic account, to get sandbox API keys from the Atomic console. If you don't have one, contact Atomic to get set up. Besides the PayLink endpoints, the keys need access to list API secrets and to create and delete webhook endpoints, for the automatic webhook setup.
  • An Anthropic account, to get an API key from the Claude Console
  • An ngrok account, for the authtoken the server uses to open a tunnel so Atomic's webhooks can reach your machine. A free account works, and you don't need the ngrok CLI.

1. Install

npm install
cd mobile && npm install && cd ..

2. Configure

Copy the example env file and fill in your keys:

cp .env.example .env
ANTHROPIC_API_KEY=sk-ant-...
ATOMIC_API_BASE_URL=https://sandbox-api.atomicfi.com
ATOMIC_API_KEY=...       # sandbox key from the Atomic console
ATOMIC_API_SECRET=...    # sandbox secret from the Atomic console
USER_IDENTIFIER=...      # required: the user every chat acts as, such as test-user-1
NGROK_AUTHTOKEN=...      # from the ngrok dashboard
NGROK_DOMAIN=            # optional: a reserved domain on a paid ngrok plan

All of these except NGROK_DOMAIN are required. If any is missing, the server stops at startup and lists the ones it needs.

Tell the app where the server is in mobile/.env:

# localhost works in the iOS simulator; on a real device use your Mac's LAN IP.
EXPO_PUBLIC_API_URL=http://localhost:3000

3. Start the server

npm run dev

It listens on http://localhost:3000 and creates data.db with one test user, USER_IDENTIFIER from .env. Every chat acts as that user. It also opens an ngrok tunnel and points Atomic's webhooks at it; see step 5.

4. Start the app

In another terminal:

npm run ios

This builds the app, installs it on the simulator and starts Metro. The first build takes a few minutes. The header shows Online once the app's WebSocket connects.

5. Receive webhooks

Webhooks are how the agent learns whether a cancellation went through. The Transact SDK's events only say that the user left the Atomic screen, not how the task ended. Without webhooks, the agent can't confirm any outcome.

The server sets them up for you when it starts:

  1. It opens an ngrok tunnel to itself, using NGROK_AUTHTOKEN. The tunnel gets a new URL every time, unless you set NGROK_DOMAIN to a domain reserved on a paid ngrok plan.
  2. It creates a new endpoint at <tunnel URL>/webhooks/atomic/manage-agent for task-status-updated, signed with your API secret named default, or your first secret if none has that name.

When the server stops, with Ctrl+C or when npm run dev restarts it after a change, it deletes the endpoint it created. If it's killed outright, the endpoint stays in Atomic, and you can delete it in the Atomic console.

The log shows the tunnel and the endpoint it registered. If any step fails, the server logs an error starting with [ngrok] or [webhooks]. The app still starts, but the agent won't hear when a cancellation finishes.

To try the flow without Atomic, you can send a webhook yourself while the app is open:

curl -X POST localhost:3000/webhooks/atomic/manage-agent -H 'content-type: application/json' -d '{
  "eventType": "task-status-updated", "product": "action", "task": "demo-task-1",
  "user": { "identifier": "<your USER_IDENTIFIER>" }, "company": { "name": "Netflix" },
  "data": { "status": "completed", "flow": "cancel-plan" }
}'

The agent's message should appear in the chat a few seconds later. Repeat deliveries of a finished task are ignored, so use a new task value each time.

About

An simple example to show to customers how our manage product can be used with an AI agent.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages