Building an Embedded App on YouCan
This guide walks through building a complete embedded app: an order tracker that lists a store's orders and sends the seller an SMS when a new order comes in. You'll scaffold the app, run it against a development store, read data from the API, receive webhooks, and take it to production.
Requirements
- A YouCan Partner account and a development store.
- Node.js, pnpm, and git.
- Familiarity with JavaScript. The template is based on Nuxt.
1. Scaffold the app
The CLI ships as a dependency of your app, there is nothing to install globally:
pnpm create @youcan/app@latestPick a name (a generated one is suggested), then select Start with Nuxt. The scaffolder clones the template, installs dependencies, and updates the YouCan packages, when it finishes, the app is ready to run:
cd <your-app-name>2. Know your way around
├── app/ # the Nuxt frontend
│ ├── composables/ # useApi (session-aware fetch), useQantra
│ ├── layouts/ # app frame
│ ├── middleware/ # global auth guard
│ ├── pages/ # index.vue is your app's landing page
│ └── plugins/ # qantra + session bootstrapping
├── server/ # the Nitro backend
│ ├── middleware/ # resolves the session on every request
│ ├── plugins/webhooks.ts # your webhook handlers
│ ├── routes/
│ │ ├── api/ # your endpoints, session-authenticated
│ │ ├── auth/ # session + token exchange, prewired
│ │ └── webhooks/ # verified webhook receiver, prewired
│ └── utils/youcan.ts # `youcan`, an API client bound to the session
├── lib/youcan/ # typed API client and resources
├── prisma/ # session storage, SQLite by default
├── shared/ # types and utils shared by app and server
├── youcan.app.json # your app's config as code, committed
└── youcan.web.json # how the CLI runs your web process2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Two files matter most day to day: youcan.app.json carries your app's name, webhooks, and later its production app_url; server/utils/youcan.ts gives every server route a youcan client already authenticated for the calling store.
3. Run it
pnpm devThe CLI authenticates you, creates the app on the platform on first run, opens a tunnel, and starts Nuxt with everything wired: Prisma migrations, environment variables, and a dev session scoped to your development store. Press i to install the app on your dev store, then p to open it in the Seller Area.
Everything you do during dev affects only your development store. Installed stores are only touched when you release a version.
4. How authentication works
The template prewires the whole flow: the Seller Area loads your app in an iframe, the frontend obtains a session token through Qantra, and the server exchanges it for a store access token kept in the Prisma-backed session. Your server routes read it from context, your frontend calls them with useApi. Read Authentication when you want the internals; you don't need to touch any of it to build.
5. Display orders
Server route first, youcan is auto-imported and already scoped to the calling store:
// server/routes/api/orders.get.ts
export default defineEventHandler(async () => {
return youcan.orders.list({ include: ['customer'] });
});2
3
4
5
Then the page:
<!-- app/pages/index.vue -->
<script setup lang="ts">
const { data: orders } = useApi('/api/orders', { server: false });
</script>
<template>
<div v-for="order in orders?.data" :key="order.id">
#{{ order.ref }} — {{ order.total }} {{ order.currency }}
</div>
</template>2
3
4
5
6
7
8
9
10
11
Open the app from the Seller Area and your dev store's orders render. useApi routes the request through your server with the session attached.
6. React to new orders
Webhooks are declared in youcan.app.json, the template already declares the one we need:
{
"webhooks": [
{ "topic": "order.created", "address": "/webhooks/order.created" },
{ "topic": "app.uninstalled", "address": "/webhooks/app.uninstalled" }
]
}2
3
4
5
6
While pnpm dev runs, your development store is subscribed and the addresses resolve to your tunnel. The template's receiver at server/routes/webhooks/[event].post.ts verifies the signature and unwraps the payload envelope, so your handler receives the order object directly:
// server/plugins/webhooks.ts
import type { Order } from '~~/lib/youcan';
export default defineNitroPlugin(() => {
defineWebhookHandler('order.created', async (payload) => {
const order = payload as Order;
await sendSMS(`New order #${order.ref}: ${order.total} ${order.currency}`);
});
});2
3
4
5
6
7
8
9
10
11
7. Send the SMS
We'll use Twilio:
pnpm add twilio// server/utils/sms.ts
import twilio from 'twilio';
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
export async function sendSMS(body: string) {
await client.messages.create({
body,
from: process.env.TWILIO_FROM,
to: process.env.TWILIO_TO,
});
}2
3
4
5
6
7
8
9
10
11
12
13
Set the three TWILIO_* variables in your .env. Utilities in server/utils are auto-imported, sendSMS is available in your webhook handler without an import.
8. Test it
Place an order on your development store's storefront. Within moments your dev console logs the delivery, your handler runs, and the SMS goes out. If the delivery doesn't show, check your app's Webhooks tab in the Partner Dashboard, it lists every delivery with its payload and your endpoint's response, and lets you redeliver.
9. Ship it
Follow From dev to production: set your production app_url in youcan.app.json, deploy your server, then
youcan app deploycreates a version and releases it, your webhook declarations follow automatically to every installed store. To list your app on the Marketplace, submit it for review from the Partner Dashboard, see Distribution & Review. Your app stays fully editable during review.
Next steps
- Qantra — toasts, navigation, and resource pickers inside the Seller Area
- Billing — charge sellers through managed billing
- Webhooks — every topic, limits, and delivery monitoring
- Command reference — every CLI command and flag