# StampyWallet Documentation > The documentation for StampyWallet, the loyalty card in Apple Wallet and Google Wallet for local businesses: the merchant guide, the MCP server and its tools, the API and webhooks, and the "beautiful cards" skill. This file gathers all the pages of https://stampywallet.com/en/docs/ as text. If in doubt, the online page prevails. --- # Welcome to the StampyWallet documentation StampyWallet is a loyalty card in Apple Wallet and Google Wallet, with no app to install. Here is everything you need to create it, scan it at the counter, and run it from an AI assistant or your point of sale. - [Merchant guide](https://stampywallet.com/en/docs/guide/create-your-card/) : Create your card, scan it, bring your customers back. - [MCP and AI assistants](https://stampywallet.com/en/docs/mcp/connect/) : Claude, ChatGPT, Cursor: connect and use each tool. - [API and webhooks](https://stampywallet.com/en/docs/api/) : Connect StampyWallet to your POS, your website or Zapier. - [skill.md](https://stampywallet.com/en/docs/skill/) : Teach your assistant to design beautiful cards. ## Quick start 1. **Create your card.** Your business name, your card type, your rewards: a few minutes on [stampywallet.com](site:#inscription), with a 30-day trial. 2. **Put the poster on your counter.** Your customers scan the QR code and the card lands in their Wallet, with no app. 3. **Scan at every visit.** With your phone: the customer's card updates by itself. ## Where to start - You are opening your account: [Create your card in 5 minutes](https://stampywallet.com/en/docs/guide/create-your-card/), then [Card types](https://stampywallet.com/en/docs/guide/card-types/). - You are at the counter: [Scan a card](https://stampywallet.com/en/docs/guide/scanning/) and [Your team](https://stampywallet.com/en/docs/guide/team/). - You want your customers to come back: [Messages and notifications](https://stampywallet.com/en/docs/guide/messages/), [Gift cards and packages](https://stampywallet.com/en/docs/guide/gift-cards-and-packages/), [Google reviews](https://stampywallet.com/en/docs/guide/google-reviews/). - You work with an AI: [connect Claude, ChatGPT or Cursor](https://stampywallet.com/en/docs/mcp/connect/), and the ["beautiful cards" skill](https://stampywallet.com/en/docs/skill/). - You are connecting a POS or another tool: [the API and webhooks](https://stampywallet.com/en/docs/api/). > **Tip :** this documentation also exists as plain text for AI assistants, at [/llms.txt](llms.txt) and [/docs/llms-full.txt](llms-full.txt). ## Frequently asked questions **Do my customers need to install an app?** No. The card goes into Apple Wallet or Google Wallet, already on their phone, when they scan the QR code on your poster. Without a Wallet, the customer gets the card by email, with a link to a web card. **What does StampyWallet cost per month?** The Essential plan costs €20 excl. VAT per month for one location, the Growth plan €40 excl. VAT per month (automatic messages, SMS, Google reviews, API, POS connections and webhooks), and the Network plan from €90 excl. VAT per month. The first 30 days are free, with every Growth feature and no commitment. **Do I need a developer?** No. The card, scanning at the counter and messages are all set up in your StampyWallet account, with no code. The API is only for connecting a POS or software, and for Square, Shopify, SumUp, Zettle, Zapier, Make and n8n, the [Connect your POS](https://stampywallet.com/en/docs/api/connect-your-pos/) page gives step-by-step settings. **Do I need a tablet or a reader at the counter?** No. Scanning is done with the business phone, an iPhone or an Android, and each team member has their own access. Source : https://stampywallet.com/en/docs/ --- # Create your card in 5 minutes Open your account, choose your card type, customize it, try it on your phone and print the counter poster. ## 1. Open your account On [stampywallet.com](site:#inscription), enter your email: you receive a six-digit code, with no password to make up. You can also continue with Google or Apple. The trial lasts **30 days**, with every feature of the Growth plan. A bank card is required to start it, and nothing is charged during the trial; you can cancel in one click. ## 2. Your business Three questions: - **The business name**, printed at the top of the card. - **You are**: café, bakery, restaurant, nails and lashes, beauty salon, barber, shop or other. This choice suggests suitable rewards, a stamp and colors. - **Your logo**, optional (PNG or JPEG). ## 3. Choose the card type Three types, each shown in two designs, already with your name and colors: | Type | The customer earns | Best for | |---|---|---| | **Stamps** | One reward after N visits | Cafés, bakeries, barbers | | **Multiple rewards** | Better and better rewards on the same card | Beauty salons, nails, restaurants | | **Points** | Points at every visit, based on what they spend | Baskets that vary a lot | Each type in detail: [Card types](https://stampywallet.com/en/docs/guide/card-types/). ## 4. Customize it The next screen shows the card live while you set it up: - **Your rewards**: up to eight, each with a photo or an icon. - **The shape** of the card, **the color** and **the logo**. - **Invite your customers to leave their number**, for SMS and Google reviews. You can change everything later at any time in **Your card**: each change saves by itself and reaches your customers' phones. ## 5. Try it on your phone "Your card is ready": add the test card to your own phone, then tap **Stamp it**. A real stamp arrives, with its notification, just like for a customer. ## 6. Print the poster In **Poster**, download the sign-up QR code: - **Counter stand (A5)** or **Small format (A6)**, as a print-ready PDF; - **the QR code alone**, as PNG or SVG, for a menu, a shop window or Canva; - **Email it to me**, to print it from another device. The poster leads to your card's public page, `stampywallet.com/rejoindre/`. This link can be made readable (`/rejoindre/le-fournil`) in **Your card › The link**, for an Instagram bio or a flyer. Changing it breaks nothing: posters already printed keep working. ## What your customer sees They scan the poster with their phone's camera, and a page opens with their card: - **Add to Apple Wallet** or **Add to Google Wallet**: their phone suggests the right one first; - or **get the card by email**, with a link to a web card. No app, no account to create. By default, only their first name is asked. > **What you ask the customer :** in **Your card › Customer sign-up**, each field is "not asked", "optional" or "required": first name, last name, birthday (day and month, never the year), email, phone. Only ask for what you use: the birthday for the birthday message, the email or phone for Google reviews and SMS. > **Tip :** want an AI to design the card for you, with photos of your business? See the ["beautiful cards" skill](https://stampywallet.com/en/docs/skill/) and the [`concevoir_carte`](https://stampywallet.com/en/docs/mcp/tools/concevoir_carte/) tool. ## Frequently asked questions **Do I need a bank card for the trial?** Yes, a bank card is required to start the 30-day trial. Nothing is charged during the trial, and you can cancel in one click. **Does the card work on iPhone and Android?** Yes. On iPhone it goes into Apple Wallet; on Android, into Google Wallet: the sign-up page suggests the right one first. A customer without a Wallet can get the card by email, with a link to a web card. **Can I change my card after launching it?** Yes, at any time in **Your card**. Every change saves itself and reaches your customers' phones. **What information is asked of my customers?** By default, only their first name. In **Your card › Customer sign-up**, last name, birthday (day and month, never the year), email and phone can each be not asked, optional or required. **Do my printed posters still work if I change the link?** Yes. Making the link readable in **Your card › The link** breaks nothing: posters already printed keep working. Source : https://stampywallet.com/en/docs/guide/create-your-card/ --- # Card types A StampyWallet card works in one of three modes, stamps, multiple rewards (tiers) or points, and each mode comes in several shapes. ## Which mode should you choose? | Mode | The customer earns | Best for | |---|---|---| | `tampons` | One stamp per visit, one reward at the end | Cafés, bakeries, barbers | | `paliers` | Several better and better rewards, on the same counter | Beauty salons, nails, florists, restaurants | | `points` | Points at every visit, based on the amount or the service | Grocery stores, wine shops, varying baskets | > **Note :** you can change mode later, in **Your card › Change type**. No customer loses their progress: their stamps stay on their card (in points mode, they count as points). Your design and colors stay too. ## Stamps The classic paper card: one stamp per visit, one reward at the end, then the card starts over. The number of slots goes from 2 to 50. ![A café stamp card, rendered by StampyWallet](https://stampywallet.com/assets/img/blog/cartes/cafe-tampons-en.webp "A stamp card, as it appears in the customer's Wallet.") Four shapes: **slots** (the grid), **no slots** (scattered stamps), **objects** (large shaded objects, ideal with a photo of your product) and **circles**. ## Multiple rewards (tiers) Up to eight rewards on the same counter, for example a coffee at 5 visits, a croissant at 10, a breakfast at 20. Each tier has its threshold, its label and its photo or icon. ![A bakery card with multiple rewards](https://stampywallet.com/assets/img/blog/cartes/boulangerie-paliers-en.webp "A tiered card: each reward at its threshold, with its photo.") Five shapes on top of stamps: **path** (a road where each visit moves the customer forward), **showcase** (rewards side by side), **medals**, **fill** (the next reward shown large, filling with color) and **ring**. ```text title="Ask the AI" Design the card for Boulangerie Lemaire with tiers: - 5 visits: a croissant - 10 visits: a baguette - 20 visits: a king cake ``` ```json title="JSON (concevoir_carte)" { "nom": "Boulangerie Lemaire", "mode": "paliers", "paliers": [ { "a": 5, "recompense": "A croissant" }, { "a": 10, "recompense": "A baguette" }, { "a": 20, "recompense": "A king cake" } ] } ``` ## Points Points at every visit, when the basket varies a lot from one customer to the next. Rewards are earned at point thresholds, with the same five shapes as tiers. ![A points card for a tea room](https://stampywallet.com/assets/img/blog/cartes/the-points-en.webp "A points card: the gauge moves forward based on what the customer spends.") In **Your card › More settings › Points**: - **Points per visit**: the default value offered by the scanner. - **Quick-add values**: up to six shortcuts, added with one tap at the counter. - **Name of your points**: "points", "seeds", "stars"… - **From the receipt total**: a number of points per euro; you type the receipt total at the counter and the calculation is done for you. > **Warning :** in points mode, the value is chosen at the counter at each scan, or arrives automatically from your POS through the [API](https://stampywallet.com/en/docs/api/add-visit/). ## Styles Six collections dress the card: **Essential**, **Classic café**, **Botanical**, **Trattoria**, **Atelier** and **Pass** (like an Apple Wallet pass: large logo, solid background). Each offers several palettes, and the color can also be custom. ## Rules In **Your card › More settings › Rules**: - **Stamps given at sign-up**: from 0 to 5. A card already started makes people want to finish it. - **Reward validity**: never expires, or 1, 2, 3, 6 or 12 months after it was earned. - **One stamp every…**: an amount (for example one stamp per €10 spent). You type the receipt total at the counter. - **Two counters**: a premium range with its own goal and reward; you choose "regular" or "premium" in the scanner. ## Frequently asked questions **What is the difference between stamps, tiers and points?** With stamps, each visit gives one stamp and a full card gives a reward, then it starts over. With tiers, up to eight better and better rewards are spread along the same counter. With points, the customer earns points based on the amount or the service, and rewards are unlocked at points thresholds. **Can I change card type later?** Yes, in **Your card › Change type**. No customer loses their progress: their stamps stay on their card, and count as points in points mode. Your design and colors stay too. **How many boxes can a stamp card have?** From 2 to 50 boxes. **Can I give a stamp based on the receipt total?** Yes. The **One stamp every…** rule gives, for example, one stamp per €10 spent: you type the receipt total at the counter. In points mode, **From the receipt total** gives a number of points per euro. **Are all three types included in the Essential plan?** Yes. Stamps, points and tiered rewards are included in every plan, starting with Essential. Source : https://stampywallet.com/en/docs/guide/card-types/ --- # Scan a card at the counter Add a stamp or points, give a reward, undo a mistake, find a customer without the camera and keep working offline. Scanning happens on the business's phone, an iPhone or an Android, with no reader or tablet. Each team member has their own access ([Your team](https://stampywallet.com/en/docs/guide/team/)). ## Add a stamp 1. **Open Scan.** The camera opens right away. 2. **Point at the customer's card**, on their phone (not the poster). 3. **Check the "Card read" screen**: the first name and the progress, for example 7 → 8 out of 10. 4. **Tap "Add the stamp".** The customer's card updates in their Wallet. "Not them" cancels the read. ![Scanning a card at the counter](https://stampywallet.com/assets/img/step-scan.webp "The business's phone reads the card in the customer's Wallet.") **Several stamps at once**: before pointing, set the number with − and +, up to 50. If your card counts by amount ("one stamp every €10"), type the receipt total instead. The value goes back to 1 after each scan. ## Add points On a points card, choose the value before pointing: the default value, a quick-add value, or "…" for another one. With the "From the receipt total" rule, switch to **Amount** and type the total: the points are calculated for you. ## Give a reward When a visit earns a reward, the screen shows **Reward earned**: - **Given**: you hand it over now; - **Later**: it stays on the customer's card, ready for their next visit. If there are several rewards to give, the next one is offered right away. ## Undo a mistake Right after a scan, the bottom bar offers **Undo**. - The owner and managers can undo a customer's last stamp, or a reward given, at any time. - A staff member can undo their own action for **5 minutes**. - Only the last visit can be undone. A stamp whose reward has already been given is undone after the reward. - A reason can be given (wrong card, scanned twice, wrong quantity, given by mistake). The cancellation is recorded under your name. ## Double scans By default, two visits from the same customer less than a minute and a half apart only count once: "Already stamped a moment ago". Set this delay in **Settings › The counter › Two visits from the same customer**: always count, one per minute, per five minutes, per hour or per half day. ## No camera: find a customer **Find a customer** finds a card by first name, last name, email, phone or the code printed under the QR code. The same search also finds gift cards (by their code) and packages. The stamp then goes through the same confirmation screen. ## Offline If the network does not respond, the stamp is kept on the phone ("Stamp saved, will be confirmed") and goes out as soon as the connection is back, with no duplicate. The scanner opens even offline, and customer search works on the list kept on the phone. > **Warning :** giving a reward and undoing a visit need a network connection. > **Tip :** install the scanner like an app: the browser offers it ("Install the app"), and it then opens with one tap, full screen. ## Birthdays at the counter When it is the customer's birthday, the screen always says so. If a birthday gift is open on their card, it is shown with the **Offer used** button (or two buttons when the customer chooses their gift). See [Messages and notifications](https://stampywallet.com/en/docs/guide/messages/#anniversaire). Source : https://stampywallet.com/en/docs/guide/scanning/ --- # Your team Invite your staff and managers, see what each role can do, and track the stamps each person adds. Unlimited team on every plan. As many accounts as there are people at the counter, at no extra cost, on every plan. Each person scans from their own phone, with their own access. ## Roles | Action | Owner | Manager | Staff | |---|---|---|---| | Stamp a card, give a reward | Yes | Yes | Yes | | See numbers and customers | Yes | Yes | No | | Message customers | Yes | Yes | No | | Change the card and settings | Yes | Yes | No | | Invite, remove, manage the subscription | Yes | No | No | A staff member only sees **Scan** and their account, plus gift cards and packages when the business sells them. A business always keeps at least one owner. ## Invite someone 1. **Open Your team › Invite someone** (owner only). 2. **Choose the role**: staff or manager. Add a label if you like ("Julie, mornings"). 3. **Send it**: with their email, the invitation goes out by email; without an email, **Create the link** then **Copy the link**, and send it however you like. An invitation is valid for **fourteen days**. You can resend it or withdraw it as long as it has not been used. A manager invitation sent to an email address can only be accepted with that email. ## Change a role, remove someone In **Your team**, change a member's role or remove their access: their sessions close immediately. ## Activity per person **See activity per person** shows, over 7 or 30 days, the stamps added, the rewards given and the cancellations of each member. > **Tip :** from a connected AI assistant, the [`equipe`](https://stampywallet.com/en/docs/mcp/tools/equipe/) tool lists the team, sends invitations and changes roles, always after your approval. Source : https://stampywallet.com/en/docs/guide/team/ --- # Messages and notifications Message your customers on their phone's lock screen, target a group, schedule a send, and let reminders, win-back messages and birthdays go out on their own. A card in the Wallet can show a notification on the customer's lock screen. It is free, on every plan, and it is what brings people back. ## Send a message In **Notifications › Send a message**: - **180 characters** at most, with a preview of what the customer will see. Three templates to get started. - **One message per day** per business, so you do not tire your customers. - Before sending, the screen tells you how many customers will receive it: those who have the card in a Wallet. A web card (received by email) does not get notifications. **To whom**: all your customers, on every plan. With the Growth plan, a group: not seen for 30, 60 or 90 days, close to their reward, birthday this month, best customers (the 20% who come most often), new customers. **Schedule** (Growth): choose the day and time of the send, up to 60 days ahead, between 7am and 10pm, in the business's time zone. A scheduled message can be edited, cancelled or sent right away. ## Automatic messages Growth plan, in **Settings**: | Message | When it goes out | Default | |---|---|---| | One stamp to go | Only one visit is missing for the reward | On | | Your reward is here | The customer has just earned a reward | On | | The win-back | A regular has stopped coming | On | | The birthday | On the customer's birthday | On | The two reminders go out after the visit, at the delay you choose: right away, 30 minutes, 1 hour (default), 3 hours or the next day. ### Win back customers you have lost A regular is "lost" when their absence goes beyond their own visit rhythm, or, without enough history, beyond your business's delay (suggested based on your trade, adjustable). Only opening days count: declare your holidays and closing days in **Settings › Your closures** so no one gets a win-back message when you reopen. One win-back per absence; its text (180 characters) can be edited. ### The birthday To turn it on, ask for the birthday at sign-up ([what you ask the customer](https://stampywallet.com/en/docs/guide/create-your-card/)). On the day, the customer receives a message and their gift, valid for 30 days: - **a message only**; - **a discount** of 1 to 90% on every purchase for 30 days; - **buy one, get one free**, once; - **the customer's choice**, between the discount and "buy one, get one free", chosen at the counter. One message per person per year, if the customer signed up before their birthday. At the counter, the scanner shows the gift with the **Offer used** button. ## Quiet hours No automatic message arrives between 9pm and 9am by default. A send that falls in that window is postponed, not lost. ## Notification, SMS, email - **The Wallet notification** is free and unlimited, on every plan. - **SMS** (Growth) takes over when the card is not in a Wallet, for customers who left their number: 200 SMS per month are included, spread over the month, with a reserve for important messages. No SMS on Sundays, public holidays or outside 8am to 8pm, never more than three per week to the same number, and never a promotion to someone who declined them. - **Email** is the fallback for Google reviews and birthdays. The channel is set per kind of message in **Settings › Messages and SMS**: automatic (recommended), notification only, SMS as fallback, SMS first, or email as fallback. ## What your messages brought back With the Growth plan, one customer in ten, picked at random, receives neither promotions nor win-back messages. Seven days after each message, the **report** compares who came back in each group: the difference is what the message really brought in. ## The Monday summary Every Monday morning, the owner receives the week's summary by email: stamps, new customers, rewards, and lost customers. No summary for a week with no activity. > **Tip :** from a connected AI assistant: [`envoyer_message`](https://stampywallet.com/en/docs/mcp/tools/envoyer_message/) for a message, [`regler_automatismes`](https://stampywallet.com/en/docs/mcp/tools/regler_automatismes/) for automatic messages, [`fermetures`](https://stampywallet.com/en/docs/mcp/tools/fermetures/) for holidays. Source : https://stampywallet.com/en/docs/guide/messages/ --- # Gift cards and packages Sell gift cards and prepaid packages at the counter, send them to the Wallet, then redeem them with a scan. Two products of the Growth plan, which live in the Wallet just like the loyalty card. In both cases, **you take payment at your register, as usual**: StampyWallet does not take the payment and sells nothing online. ## Gift cards ![Two StampyWallet gift cards](https://stampywallet.com/assets/img/blog/cartes/cadeaux-duo-en.webp "A product gift card and an amount gift card, in the business's colors.") ### Get ready to sell In **Gift cards › Settings**: - **products**, with their price and photo (up to 24); - **amounts** (up to 8), and whether you allow a custom amount; - **validity**: 3, 6 or 12 months after the sale. Then **Start selling**. The gift card uses the logo and colors of your loyalty card. ### Sell 1. **Take payment** at your register. 2. **Sell a gift card**: the product or the amount, "For", "From", a short note. Staff members can sell too. 3. **Hand it over**: a QR code to scan, a link to share (WhatsApp, SMS), or StampyWallet sends it by SMS or email. The recipient adds it to Apple Wallet or Google Wallet, or keeps it on its web page. ### Redeem at the counter Scan the gift card, or look up its 8-character code: - a **product** card: **Give**, and it is used; - an **amount** card: **Charge** the amount you want; the balance before and after is shown, and the balance updates in the Wallet. A card already sold is always honored on every plan, even if you stop selling. ## Prepaid packages Ten sessions, ten coffees, five treatments: the customer pays up front, and each visit deducts one unit. - From 1 to 100 units, with the word of your choice (session, treatment, coffee…). - Up to 12 package templates, valid for 1, 2, 3, 6 or 12 months, or with no limit. - Selling: take payment, then **Payment taken, create the package**. The customer gets the package in their Wallet. - At the counter: **Deduct 1 session** (or your unit). The last deduction can be undone for 24 hours. - The Wallet reminds the customer one week before expiry, and when only a few units are left. Source : https://stampywallet.com/en/docs/guide/gift-cards-and-packages/ --- # Google reviews Ask every customer for a Google review at the right moment, with a direct link to your listing, no filtering and no reward, within Google's rules. With the Growth plan, StampyWallet asks your customers for a Google review when they are happiest: at their first reward, or after a few visits. ## How it works - **The same message for everyone**, with the direct link to your Google listing. - **No rating step in between**, no sorting customers by their opinion, **no reward** in exchange for a review: these are Google's rules, and StampyWallet follows them. - **One request per customer**, only once. ## Turn it on 1. **Paste your Google review link** in **Google reviews**. Without it, nothing is sent. You will find it in your Google Business Profile, under the "Ask for reviews" button. 2. **Ask for the email or phone number** at sign-up ([Create your card](https://stampywallet.com/en/docs/guide/create-your-card/)): the request goes out by SMS or by email. 3. **Choose the moment**: at the first reward (default) or after a number of visits (3 by default). 4. **Choose the delay** after the visit: right away, 30 minutes, 1 hour, 3 hours (default) or the next day. Quiet hours apply. > **Note :** a Wallet notification cannot carry a link. So the request goes out first by SMS (within your budget of 200 SMS per month), or by email depending on your setting in **Settings › Messages and SMS**. The link is also on the back of the customer's card. ## The numbers The **Google reviews** screen shows the requests sent and the clicks to Google over the last 30 days. > **Tip :** from a connected AI assistant, the [`avis_google`](https://stampywallet.com/en/docs/mcp/tools/avis_google/) tool reads these numbers and sets up the request. Source : https://stampywallet.com/en/docs/guide/google-reviews/ --- # Connect an AI assistant The StampyWallet MCP server connects Claude, ChatGPT, Cursor or an agent to your loyalty card. One address, no key, and nothing changes without your approval. StampyWallet speaks the **Model Context Protocol** (MCP), the standard that lets an AI assistant use tools. Once connected, you can ask in a conversation: "design me a loyalty card", "how many customers came this month?", "tell my regulars I'm closed on Monday". ## The server address ```text title="MCP server" https://app.stampywallet.com/mcp ``` Streamable HTTP, JSON responses, stateless. It is the only thing you need to give your assistant. **Claude** **Settings › Connectors › Add custom connector**, paste the address. Step by step: [Claude](https://stampywallet.com/en/docs/mcp/claude/). **ChatGPT** **Settings › Apps and connectors**, developer mode, **Create**, paste the address. Step by step: [ChatGPT](https://stampywallet.com/en/docs/mcp/chatgpt/). **Cursor and agents** ```json title=".cursor/mcp.json" { "mcpServers": { "stampywallet": { "url": "https://app.stampywallet.com/mcp" } } } ``` Step by step, plus Claude Code: [Cursor and agents](https://stampywallet.com/en/docs/mcp/cursor/). ## Without an account, then with your account The server offers two families of tools at the same address: - **Without an account**: [`concevoir_carte`](https://stampywallet.com/en/docs/mcp/tools/concevoir_carte/) designs a card that looks like a business, [`simuler_fidelite`](https://stampywallet.com/en/docs/mcp/tools/simuler_fidelite/) works out what it costs, and [`creer_commerce`](https://stampywallet.com/en/docs/mcp/tools/creer_commerce/) puts it live and returns a link to take ownership of it. No sign-in is needed. - **With your account**: your numbers, your customers, your messages, Google reviews, card settings, the poster, the team. On the first call, the assistant opens the StampyWallet sign-in page (OAuth): you sign in, choose the business, and allow access. On the authorization screen, you can tick **read only**: the assistant reads your numbers and settings, but cannot change anything. Access is managed in **Settings › Your data › Connected assistants**, where each assistant can be turned off in one click. | Scope | What it allows | |---|---| | `stampy.lecture` | Read your numbers, your customers (first names), your settings and your card | | `stampy.ecriture` | Act for you: messages to customers, settings, card, team, always after your approval in the conversation | ## Nothing changes without your approval Tools that write take a `confirmer` parameter, `false` by default. Called that way, they do nothing: they return a **preview** (the exact text of a message and the number of recipients, the before and after of a setting). The assistant shows it to you, and only calls the tool again with `confirmer: true` after your "yes". > **Warning :** two things can only be done at the counter, by scanning the customer's card: **adding stamps** and **giving rewards**. No MCP tool does it. To add them from a POS, there is the [API](https://stampywallet.com/en/docs/api/add-visit/). ## The tools | Tool | Account | Effect | |---|---|---| | [`concevoir_carte`](https://stampywallet.com/en/docs/mcp/tools/concevoir_carte/) | No | Read only | | [`simuler_fidelite`](https://stampywallet.com/en/docs/mcp/tools/simuler_fidelite/) | No | Read only | | [`creer_commerce`](https://stampywallet.com/en/docs/mcp/tools/creer_commerce/) | No | Writes (confirm) | | [`tableau_de_bord`](https://stampywallet.com/en/docs/mcp/tools/tableau_de_bord/) | Yes | Read only | | [`rentabilite`](https://stampywallet.com/en/docs/mcp/tools/rentabilite/) | Yes | Read only | | [`meilleurs_moments`](https://stampywallet.com/en/docs/mcp/tools/meilleurs_moments/) | Yes | Read only | | [`chercher_clients`](https://stampywallet.com/en/docs/mcp/tools/chercher_clients/) | Yes | Read only | | [`fiche_client`](https://stampywallet.com/en/docs/mcp/tools/fiche_client/) | Yes | Read only | | [`envoyer_message`](https://stampywallet.com/en/docs/mcp/tools/envoyer_message/) | Yes | Writes (confirm) | | [`historique_messages`](https://stampywallet.com/en/docs/mcp/tools/historique_messages/) | Yes | Read only | | [`regler_automatismes`](https://stampywallet.com/en/docs/mcp/tools/regler_automatismes/) | Yes | Writes (confirm) | | [`fermetures`](https://stampywallet.com/en/docs/mcp/tools/fermetures/) | Yes | Writes (confirm) | | [`avis_google`](https://stampywallet.com/en/docs/mcp/tools/avis_google/) | Yes | Writes (confirm) | | [`voir_carte`](https://stampywallet.com/en/docs/mcp/tools/voir_carte/) | Yes | Read only | | [`modifier_carte`](https://stampywallet.com/en/docs/mcp/tools/modifier_carte/) | Yes | Writes (confirm) | | [`fiche_commerce`](https://stampywallet.com/en/docs/mcp/tools/fiche_commerce/) | Yes | Writes (confirm) | | [`affichette`](https://stampywallet.com/en/docs/mcp/tools/affichette/) | Yes | Read only | | [`envoyer_affichette`](https://stampywallet.com/en/docs/mcp/tools/envoyer_affichette/) | Yes | Writes (confirm) | | [`equipe`](https://stampywallet.com/en/docs/mcp/tools/equipe/) | Yes | Writes (confirm) | Tools that need an account also follow your role (a staff member has access to none of them; [`equipe`](https://stampywallet.com/en/docs/mcp/tools/equipe/) is for the owner only) and your plan (some numbers and Google reviews need Growth). ## Try it by hand The server answers any MCP client, or `curl`: ```bash title="curl" curl https://app.stampywallet.com/mcp \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' ``` Without an account, calls are rate limited per IP address: beyond the limit, the server asks you to wait a few minutes. ## Frequently asked questions **Do I need a key to connect the MCP server?** No, no key at all. The tools that need no account work right away; for your figures and your customers, the assistant asks you to connect your StampyWallet account on the first call. **Does it work with free ChatGPT?** On the StampyWallet side, there is nothing to pay to connect the server, and the tools that need no account are open to everyone. On the ChatGPT side, the connector is created in developer mode, and access to it depends on your ChatGPT plan. If yours does not offer it, Claude, Cursor or any other MCP client connects to the same address. **Can the assistant change anything without my approval?** No. Tools that write first return a preview, and the assistant only calls them again with `confirmer: true` after your "yes". You can also allow read-only access. **Can the assistant add stamps?** No. Adding stamps and giving rewards is only done at the counter, by scanning the customer's card. From a POS, the [API](https://stampywallet.com/en/docs/api/add-visit/) does it. **How do I cut off an assistant's access?** In **Settings › Your data › Connected assistants**: each assistant is cut off in one click. Source : https://stampywallet.com/en/docs/mcp/connect/ --- # Connect Claude Add StampyWallet as a custom connector in Claude (web, desktop and mobile), then connect your account. 1. **Open Claude** (claude.ai or the desktop app), then **Settings › Connectors**. 2. **Add custom connector.** Name: `StampyWallet`. Address: `https://app.stampywallet.com/mcp`. 3. **Turn it on in a conversation**, from the tools menu under the message box. 4. **Ask**: "Design the loyalty card for my café, Le Petit Zinc, buy 8 coffees, get the 9th free." Claude calls [`concevoir_carte`](https://stampywallet.com/en/docs/mcp/tools/concevoir_carte/) and shows you the card. 5. **For your numbers and customers**, Claude offers to sign you in: the StampyWallet page opens, you choose the business and allow access. On a team plan, adding a custom connector may be limited to the organization's admins. ## With the skill Also add the ["beautiful cards" skill](https://stampywallet.com/en/docs/skill/): Claude will look for the business's real photos and offer at least twelve versions of the card, across all types. ## Example requests - "How many customers came this month, and is it better than last month?" ([`tableau_de_bord`](https://stampywallet.com/en/docs/mcp/tools/tableau_de_bord/)) - "Who are my regulars who stopped coming?" ([`chercher_clients`](https://stampywallet.com/en/docs/mcp/tools/chercher_clients/)) - "Tell all my customers I'm closed from December 24 to 31." ([`fermetures`](https://stampywallet.com/en/docs/mcp/tools/fermetures/)) - "Switch my card to tiers: a coffee at 5, a croissant at 10." ([`modifier_carte`](https://stampywallet.com/en/docs/mcp/tools/modifier_carte/)) Source : https://stampywallet.com/en/docs/mcp/claude/ --- # Connect ChatGPT Add the StampyWallet MCP server as a connector in ChatGPT, then connect your StampyWallet account. 1. **Open ChatGPT on the web**, then **Settings › Apps and connectors**. 2. **Turn on developer mode** in the advanced settings (depending on your plan, it may be limited to workspace admins). 3. **Create a connector.** Name: `StampyWallet`. MCP server address: `https://app.stampywallet.com/mcp`. Authentication: OAuth. 4. **In a conversation**, add StampyWallet from the "+" menu, then ask: "Design the loyalty card for my bakery." 5. **For your numbers and customers**, ChatGPT shows **Sign in**: the StampyWallet page opens, you choose the business and allow access. > **Note :** in ChatGPT, some tools are shown with their own screen (the rendered card, the numbers). The same tools work everywhere else as text and data. ## In a ChatGPT Project Paste the content of [skill.md](https://stampywallet.com/en/docs/skill/) into a Project's instructions: every conversation in the project will follow the beautiful cards method. Source : https://stampywallet.com/en/docs/mcp/chatgpt/ --- # Connect Cursor and agents Declare the StampyWallet MCP server in Cursor, Claude Code, VS Code or any MCP-compatible agent. Any MCP client that speaks streamable HTTP connects with the same address: `https://app.stampywallet.com/mcp`. Account sign-in goes through OAuth (dynamic registration, PKCE): the client opens the StampyWallet page in your browser on the first tool that needs it. **Cursor** In **Cursor Settings › MCP › Add new MCP server**, or in the project file: ```json title=".cursor/mcp.json" { "mcpServers": { "stampywallet": { "url": "https://app.stampywallet.com/mcp" } } } ``` **Claude Code** ```bash title="Terminal" claude mcp add --transport http stampywallet https://app.stampywallet.com/mcp ``` Then `/mcp` in Claude Code to sign in to your account. **VS Code** ```json title=".vscode/mcp.json" { "servers": { "stampywallet": { "type": "http", "url": "https://app.stampywallet.com/mcp" } } } ``` **Other agent** Declare a remote HTTP MCP server at `https://app.stampywallet.com/mcp`. The OAuth metadata is published at `https://app.stampywallet.com/.well-known/oauth-protected-resource`. ## An agent that designs cards Give the agent the raw address of the skill, `https://stampywallet.com/en/docs/skill.md`, and ask: "Read this skill, then design the loyalty cards for this business: [name, city, Instagram link]." See [skill.md](https://stampywallet.com/en/docs/skill/). Source : https://stampywallet.com/en/docs/mcp/cursor/ --- # concevoir_carte No account · Read only. Designs a StampyWallet loyalty card (Apple Wallet and Google Wallet) for a business, with no account and without saving anything. Use it when someone wants a loyalty card, wants to keep customers coming back, or wants to see what their card would look like. All parameters are optional: without them, a card suited to the trade is suggested. The logo (logo_url) and the photo of each reward (paliers[].image_url) are given by their https address; they are shown on the preview, without being saved. Call the tool again with the returned settings, modified, to refine. When the person is happy, suggest creer_commerce with the same settings. ## Parameters - `nom` (string, ≤ 80 characters, optional) : Business name, printed at the top of the card. - `metier` (enum, optional) : Type of business. Picks a suitable style, stamp and starting reward when they are not given. "autre": beauty salons, nails, florists, hairdressers, grocery stores… Values : cafe, boulangerie, restaurant, autre. - `mode` (enum, optional) : tampons: one reward at one threshold (the simplest). paliers: several rewards on the same card (e.g. 5 and 10). points: the business enters a number of points at each visit (varying baskets). Values : tampons, paliers, points. - `recompense` (string, ≤ 120 characters, optional) : The reward (tampons mode). Ignored if "paliers" is given. - `tampons` (integer, 2…30, optional) : Number of visits to earn the reward (tampons mode), from 2 to 30. - `paliers` (array, ≤ 6, optional) : The rewards and their thresholds (paliers and points modes). From smallest to largest. - `paliers[].a` (integer, 1…1000000, required) : Threshold: number of stamps (or points in points mode) to reach this tier. - `paliers[].recompense` (string, ≤ 120 characters, required) : What the customer gets, as printed on the card. E.g. "A free coffee". - `paliers[].icone` (enum, optional) : Icon for the tier (default: cadeau). Values : etincelle, eclat, goutte, main, pinceau, oeil, ciseaux, sechoir, fleur, lotus, coeur, etoile, diamant, couronne, soleil, lune, talon, robe, sac, serviette, cafe, grain, the, cookie, gateau, pain, part, burger, glace, bol, plat, verre, biere, bulles, cocktail, cerises, orange, avocat, piment, oeuf, poisson, fromage, popcorn, feuille, flamme, eclair, sourire, patte, chien, chat, cadeau, haltere, velo, livre, note, appareil, palette, dent, parapluie, ballon, tshirt, basket, croissant. - `paliers[].image_url` (string, ≤ 2000 characters, optional) : Photo of the reward, by its https address (PNG, JPEG or WebP, 5 MB at most, at least 64 px per side). Cut out when possible, it replaces the icon on the card: in gray before it is earned, in color after. Drawn in paliers and points modes. - `collection` (enum, optional) : Card style: original (simple), cafe (thin frame), botanique (italic, foliage), trattoria (scalloped), atelier (modern), pass (like an Apple Wallet pass: large logo, stamps as shaded objects, solid background). Values : original, cafe, botanique, trattoria, atelier, pass. - `palette` (integer, 0…3, optional) : Palette of the collection: 0, 1 or 2 (each collection offers three; pass offers a fourth, 3). Ignored if "couleur" is given. - `couleur` (string, optional) : Custom background color, in hexadecimal. The ink (text) color is chosen automatically to stay readable. - `tampon` (enum, optional) : The mark added at each visit. "rond": the simple dot. Beauty: etincelle, eclat, goutte, main, pinceau, oeil, ciseaux, sechoir, fleur, lotus, coeur, etoile, diamant, couronne, soleil, lune, talon, robe, sac, serviette. Food: cafe, grain, the, cookie, gateau, pain, part, burger, glace, bol, plat, verre, biere, bulles, cocktail, cerises, orange, avocat, piment, oeuf, poisson, fromage, popcorn. Other: feuille, flamme, eclair, sourire, patte, chien, chat, cadeau, haltere, velo, livre, note, appareil, palette, dent, parapluie, ballon, tshirt, basket. Values : rond, etincelle, eclat, goutte, main, pinceau, oeil, ciseaux, sechoir, fleur, lotus, coeur, etoile, diamant, couronne, soleil, lune, talon, robe, sac, serviette, cafe, grain, the, cookie, gateau, pain, part, burger, glace, bol, plat, verre, biere, bulles, cocktail, cerises, orange, avocat, piment, oeuf, poisson, fromage, popcorn, feuille, flamme, eclair, sourire, patte, chien, chat, cadeau, haltere, velo, livre, note, appareil, palette, dent, parapluie, ballon, tshirt, basket, croissant. - `tampons_bienvenue` (integer, 0…5, optional) : Stamps given when the customer signs up (0 to 5). 1 by default: a card already started makes people want to finish it. - `nom_des_points` (string, ≤ 20 characters, optional) : Points mode: what the points are called ("points", "seeds", "stars"…). - `forme_paliers` (enum, optional) : Paliers mode: the card design. tampons (default): stamps in a grid, each reward in place of the stamp at its threshold. Or one of the five points shapes, which then counts stamps: medailles (a gauge and a medal per reward), vitrine (rewards side by side, each with its gauge), remplissage (the next reward shown large, filling with color), chemin (an S-shaped road where each stamp moves the marker forward), anneau (a round gauge around the next reward). Reward photos are uploaded in the app, Card page. Values : tampons, medailles, vitrine, remplissage, chemin, anneau. - `forme_points` (enum, optional) : Points mode: the card design. medailles (default): a thick gauge and a large medal per reward. vitrine: rewards side by side, each with a small gauge. remplissage: the next reward shown large, filling with color as it goes. chemin: an S-shaped road, with rewards as steps. anneau: a round gauge around the next reward, with the list next to it. Reward photos are uploaded in the app, Card page. Values : medailles, vitrine, remplissage, chemin, anneau. - `forme` (enum, optional) : The card design, in one word (replaces forme_points and forme_paliers). Points and paliers modes: medailles (a gauge and a large medal per reward), vitrine (rewards side by side, each with its gauge), remplissage (the next reward shown large, filling with color), chemin (an S-shaped road, each visit moves the marker forward), anneau (a round gauge around the next reward). Tampons and paliers modes: the stamp layout, grille (default), libre (scattered), objets (large shaded objects, ideal with a photo), cercles. In paliers mode, a layout keeps the stamps and places each reward in place of the stamp at its threshold. One of the five shapes with no mode given switches the card to paliers mode. Values : medailles, vitrine, remplissage, chemin, anneau, grille, libre, objets, cercles. - `encre` (string, optional) : Card text color, in hexadecimal (default: chosen to stay readable on the background). - `accent` (string, optional) : Accent color: stamps added, gauges, labels, in hexadecimal (default: taken from the collection). - `logo_url` (string, ≤ 2000 characters, optional) : Business logo, by its https address (PNG, JPEG or WebP, 5 MB at most): its Instagram profile picture works fine. Cut out when possible, placed at the top of the card; with no couleur, palette, encre or accent given, the card takes the logo's colors. - `variantes` (boolean, optional) : true: also renders the three palettes of the chosen collection, to offer a choice. Default : false. - `tampons_poses` (integer, 0…1000000, optional) : Stamps already added on the preview, to show a card in progress (default: welcome stamps + 2). ## Example Prompt : « Design the loyalty card for Boulangerie Lemaire: 10 visits, the 10th baguette free, pass style, with its logo. » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "concevoir_carte", "arguments": { "nom": "Boulangerie Lemaire", "metier": "boulangerie", "mode": "tampons", "tampons": 10, "recompense": "The 10th baguette free", "collection": "pass", "logo_url": "https://example.com/logo-lemaire.png", "variantes": true } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/concevoir_carte/ --- # simuler_fidelite No account · Read only. Estimates what a stamp loyalty card costs (the reward compared with the basket) and how many extra visits make it pay off. Use it to choose a reward and a number of visits, or when someone wonders whether a card is worth it. No account needed. ## Parameters - `panier_moyen` (number, 0.5…10000, required) : Average spend per customer per visit, in euros. - `cout_recompense` (number, 0…10000, required) : What the reward costs THE BUSINESS (cost price, not selling price), in euros. - `tampons` (integer, 2…30, required) : Visits needed to earn the reward. - `tampons_bienvenue` (integer, 0…5, optional) : Stamps given at sign-up. Default : 1. - `marge` (number, 1…100, optional) : Gross margin on a basket, as a percentage (default 65%, typical for food service and services). Default : 65. - `clients_fideles` (integer, 1…100000, optional) : Customers who would take the card each month (default 60). Default : 60. - `visites_par_mois` (number, 0.1…60, optional) : Visits per month by a loyal customer today (default 2). Default : 2. - `comparer` (array, ≤ 5, optional) : Other numbers of visits to compare (e.g. [6, 10, 12]). ## Example Prompt : « My average basket is €6 and a coffee costs me €0.40. How much does a 10-visit card cost, and is it better at 8 or 12? » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "simuler_fidelite", "arguments": { "panier_moyen": 6, "cout_recompense": 0.4, "tampons": 10, "comparer": [ 8, 12 ] } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/simuler_fidelite/ --- # creer_commerce No account · Writes (confirm). Creates the business on StampyWallet with the designed card (same parameters as concevoir_carte), and returns a personal link to take ownership of it: the person clicks, signs in with their email, and finds everything ready (card, QR code to print, free trial). The logo (logo_url) and the photo of each reward (paliers[].image_url), given by their https address, are placed on the card. No account needed. Call first with confirmer=false to show the summary, then with confirmer=true once the person has said yes. ## Parameters - `nom` (string, ≤ 80 characters, required) : Business name, printed at the top of the card. - `metier` (enum, optional) : Type of business. Picks a suitable style, stamp and starting reward when they are not given. "autre": beauty salons, nails, florists, hairdressers, grocery stores… Values : cafe, boulangerie, restaurant, autre. - `mode` (enum, optional) : tampons: one reward at one threshold (the simplest). paliers: several rewards on the same card (e.g. 5 and 10). points: the business enters a number of points at each visit (varying baskets). Values : tampons, paliers, points. - `recompense` (string, ≤ 120 characters, optional) : The reward (tampons mode). Ignored if "paliers" is given. - `tampons` (integer, 2…30, optional) : Number of visits to earn the reward (tampons mode), from 2 to 30. - `paliers` (array, ≤ 6, optional) : The rewards and their thresholds (paliers and points modes). From smallest to largest. - `paliers[].a` (integer, 1…1000000, required) : Threshold: number of stamps (or points in points mode) to reach this tier. - `paliers[].recompense` (string, ≤ 120 characters, required) : What the customer gets, as printed on the card. E.g. "A free coffee". - `paliers[].icone` (enum, optional) : Icon for the tier (default: cadeau). Values : etincelle, eclat, goutte, main, pinceau, oeil, ciseaux, sechoir, fleur, lotus, coeur, etoile, diamant, couronne, soleil, lune, talon, robe, sac, serviette, cafe, grain, the, cookie, gateau, pain, part, burger, glace, bol, plat, verre, biere, bulles, cocktail, cerises, orange, avocat, piment, oeuf, poisson, fromage, popcorn, feuille, flamme, eclair, sourire, patte, chien, chat, cadeau, haltere, velo, livre, note, appareil, palette, dent, parapluie, ballon, tshirt, basket, croissant. - `paliers[].image_url` (string, ≤ 2000 characters, optional) : Photo of the reward, by its https address (PNG, JPEG or WebP, 5 MB at most, at least 64 px per side). Cut out when possible, it replaces the icon on the card: in gray before it is earned, in color after. Drawn in paliers and points modes. - `collection` (enum, optional) : Card style: original (simple), cafe (thin frame), botanique (italic, foliage), trattoria (scalloped), atelier (modern), pass (like an Apple Wallet pass: large logo, stamps as shaded objects, solid background). Values : original, cafe, botanique, trattoria, atelier, pass. - `palette` (integer, 0…3, optional) : Palette of the collection: 0, 1 or 2 (each collection offers three; pass offers a fourth, 3). Ignored if "couleur" is given. - `couleur` (string, optional) : Custom background color, in hexadecimal. The ink (text) color is chosen automatically to stay readable. - `tampon` (enum, optional) : The mark added at each visit. "rond": the simple dot. Beauty: etincelle, eclat, goutte, main, pinceau, oeil, ciseaux, sechoir, fleur, lotus, coeur, etoile, diamant, couronne, soleil, lune, talon, robe, sac, serviette. Food: cafe, grain, the, cookie, gateau, pain, part, burger, glace, bol, plat, verre, biere, bulles, cocktail, cerises, orange, avocat, piment, oeuf, poisson, fromage, popcorn. Other: feuille, flamme, eclair, sourire, patte, chien, chat, cadeau, haltere, velo, livre, note, appareil, palette, dent, parapluie, ballon, tshirt, basket. Values : rond, etincelle, eclat, goutte, main, pinceau, oeil, ciseaux, sechoir, fleur, lotus, coeur, etoile, diamant, couronne, soleil, lune, talon, robe, sac, serviette, cafe, grain, the, cookie, gateau, pain, part, burger, glace, bol, plat, verre, biere, bulles, cocktail, cerises, orange, avocat, piment, oeuf, poisson, fromage, popcorn, feuille, flamme, eclair, sourire, patte, chien, chat, cadeau, haltere, velo, livre, note, appareil, palette, dent, parapluie, ballon, tshirt, basket, croissant. - `tampons_bienvenue` (integer, 0…5, optional) : Stamps given when the customer signs up (0 to 5). 1 by default: a card already started makes people want to finish it. - `nom_des_points` (string, ≤ 20 characters, optional) : Points mode: what the points are called ("points", "seeds", "stars"…). - `forme_paliers` (enum, optional) : Paliers mode: the card design. tampons (default): stamps in a grid, each reward in place of the stamp at its threshold. Or one of the five points shapes, which then counts stamps: medailles (a gauge and a medal per reward), vitrine (rewards side by side, each with its gauge), remplissage (the next reward shown large, filling with color), chemin (an S-shaped road where each stamp moves the marker forward), anneau (a round gauge around the next reward). Reward photos are uploaded in the app, Card page. Values : tampons, medailles, vitrine, remplissage, chemin, anneau. - `forme_points` (enum, optional) : Points mode: the card design. medailles (default): a thick gauge and a large medal per reward. vitrine: rewards side by side, each with a small gauge. remplissage: the next reward shown large, filling with color as it goes. chemin: an S-shaped road, with rewards as steps. anneau: a round gauge around the next reward, with the list next to it. Reward photos are uploaded in the app, Card page. Values : medailles, vitrine, remplissage, chemin, anneau. - `forme` (enum, optional) : The card design, in one word (replaces forme_points and forme_paliers). Points and paliers modes: medailles (a gauge and a large medal per reward), vitrine (rewards side by side, each with its gauge), remplissage (the next reward shown large, filling with color), chemin (an S-shaped road, each visit moves the marker forward), anneau (a round gauge around the next reward). Tampons and paliers modes: the stamp layout, grille (default), libre (scattered), objets (large shaded objects, ideal with a photo), cercles. In paliers mode, a layout keeps the stamps and places each reward in place of the stamp at its threshold. One of the five shapes with no mode given switches the card to paliers mode. Values : medailles, vitrine, remplissage, chemin, anneau, grille, libre, objets, cercles. - `encre` (string, optional) : Card text color, in hexadecimal (default: chosen to stay readable on the background). - `accent` (string, optional) : Accent color: stamps added, gauges, labels, in hexadecimal (default: taken from the collection). - `logo_url` (string, ≤ 2000 characters, optional) : Business logo, by its https address (PNG, JPEG or WebP, 5 MB at most): its Instagram profile picture works fine. Cut out when possible, placed at the top of the card; with no couleur, palette, encre or accent given, the card takes the logo's colors. - `panier_moyen` (number, 0.5…10000, optional) : Average spend per customer, in euros (used for the dashboard estimates). - `langue` (enum, optional) : Language of the returned texts. By default, the business's language. Values : fr, en, es. - `confirmer` (boolean, optional) : false (default): only returns a preview of what would be done, without changing anything. true: carries it out. Always show the preview to the person and wait for their approval before calling again with true. Default : false. ## Example Prompt : « That's perfect, put this card live for Boulangerie Lemaire. » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "creer_commerce", "arguments": { "nom": "Boulangerie Lemaire", "metier": "boulangerie", "mode": "tampons", "tampons": 10, "recompense": "The 10th baguette free", "collection": "pass", "confirmer": false } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/creer_commerce/ --- # tableau_de_bord With account · Read only. The business's loyalty numbers over a chosen period: visits at the counter, customers who came, new customers, return rate, rewards earned and given, estimated revenue, with a curve by day, week or month and, if needed, a comparison with the previous period of the same length. Use it for "how is it going this month?", "how many customers in August?", "is it going up?". For the list of customers, use chercher_clients; to know whether the card pays off, rentabilite. ## Parameters - `periode` (enum, optional) : Period studied, in days of the business's time zone, today included for ongoing periods (ce_mois = from the 1st to today). "personnalisee" uses du/au. Default derniers_30_jours. Values : aujourdhui, hier, derniers_7_jours, derniers_30_jours, derniers_90_jours, derniers_12_mois, cette_semaine, semaine_derniere, ce_mois, mois_dernier, cette_annee, annee_derniere, depuis_le_debut, personnalisee. Default : "derniers_30_jours". - `du` (string (date), optional) : First day included (YYYY-MM-DD). Giving it is enough: the period becomes "personnalisee". - `au` (string (date), optional) : Last day included (YYYY-MM-DD). Default: today. - `comparer` (boolean, optional) : Adds the change compared with the period of the same length just before (e.g. the 30 days before the last 30 days). Default : true. - `indicateurs` (array, ≤ 9, optional) : Indicators to return, in this order. Default: all. taux_retour = share of customers who came in the period and had already come before; chiffre_estime = visits × declared average basket (an estimate, not a register total). Values : passages, clients_venus, nouveaux_clients, taux_retour, passages_par_client, recompenses_gagnees, recompenses_offertes, chiffre_estime, total_clients. - `courbe` (enum, optional) : What the curve plots. Default passages. Values : passages, clients_venus, nouveaux_clients, recompenses_gagnees, recompenses_offertes, aucune. Default : "passages". - `granularite` (enum, optional) : Curve step. auto (default): day up to 2 months, week up to ~9 months, month beyond. Values : auto, jour, semaine, mois. Default : "auto". ## Example Prompt : « How is it going this month, compared with last month? » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "tableau_de_bord", "arguments": { "periode": "ce_mois", "comparer": true } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/tableau_de_bord/ --- # rentabilite With account · Read only · Growth plan. Tells whether the loyalty card pays off, over a chosen period: share of customers who come back, return visits and what they represent in euros (estimate: visits × average basket), median time between two visits, rewards given and their cost if provided, rewards still to hand out, and results of the automatic win-back message. The average basket and the cost of a reward can be adjusted to redo the calculation. Use it for "is it worth it?", "how much does it bring in?", "do my customers come back?". For the raw numbers of a period, tableau_de_bord. ## Parameters - `periode` (enum, optional) : Period studied, in days of the business's time zone, today included for ongoing periods (ce_mois = from the 1st to today). "personnalisee" uses du/au. Default derniers_90_jours. Values : aujourdhui, hier, derniers_7_jours, derniers_30_jours, derniers_90_jours, derniers_12_mois, cette_semaine, semaine_derniere, ce_mois, mois_dernier, cette_annee, annee_derniere, depuis_le_debut, personnalisee. Default : "derniers_90_jours". - `du` (string (date), optional) : First day included (YYYY-MM-DD). Giving it is enough: the period becomes "personnalisee". - `au` (string (date), optional) : Last day included (YYYY-MM-DD). Default: today. - `panier_moyen` (number, 0.1…10000, optional) : Average basket in euros for this calculation. Default: the one declared in the business settings. - `cout_recompense` (number, 0…10000, optional) : What ONE reward costs the business (cost price), in euros. Without it, the cost of rewards is not calculated. - `comparer` (boolean, optional) : Adds the period of the same length just before, to see whether it is improving. Default : false. ## Example Prompt : « Does my loyalty card pay off? A reward costs me €1.20. » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "rentabilite", "arguments": { "periode": "derniers_90_jours", "cout_recompense": 1.2 } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/rentabilite/ --- # meilleurs_moments With account · Read only · Growth plan. Shows when customers come: average visits per day of the week, breakdown by hour, or the busiest time slots (day + hour), over a chosen period and in the business's local time. Can also count new customer sign-ups. Use it for "what is my best day?", "what time am I busiest?", "when should I run an offer to fill a quiet time?". ## Parameters - `par` (enum, optional) : jour_semaine (default): average per Monday, Tuesday…; heure: total per hour of the day; creneau: the busiest day + hour pairs. Values : jour_semaine, heure, creneau. Default : "jour_semaine". - `mesure` (enum, optional) : passages (default) or nouveaux_clients (card sign-ups). Values : passages, nouveaux_clients. Default : "passages". - `jours` (array, ≤ 7, optional) : For par=heure or creneau: only keep these days of the week (e.g. ["samedi"]). Values : lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche. - `periode` (enum, optional) : Period studied, in days of the business's time zone, today included for ongoing periods (ce_mois = from the 1st to today). "personnalisee" uses du/au. Default derniers_90_jours. Values : aujourdhui, hier, derniers_7_jours, derniers_30_jours, derniers_90_jours, derniers_12_mois, cette_semaine, semaine_derniere, ce_mois, mois_dernier, cette_annee, annee_derniere, depuis_le_debut, personnalisee. Default : "derniers_90_jours". - `du` (string (date), optional) : First day included (YYYY-MM-DD). Giving it is enough: the period becomes "personnalisee". - `au` (string (date), optional) : Last day included (YYYY-MM-DD). Default: today. - `limite` (integer, 1…50, optional) : For par=creneau: number of time slots returned (default 10). Default : 10. ## Example Prompt : « What time am I busiest on Saturdays? » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "meilleurs_moments", "arguments": { "par": "heure", "jours": [ "samedi" ] } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/meilleurs_moments/ --- # chercher_clients With account · Read only. Lists the business's customers by segment (regulars, lost, one stamp away from the reward, reward to give, new, inactive, never came back) combined with filters: first name, stamps on the card, number of visits, sign-up date, last visit date, wallet type; sorted and paginated. Returns for each an identifier, the first name and last name initial, visits, last visit and card status, never an email or phone number. Use it for "who are my best customers?", "who hasn't come back?", "who is close to their reward?", or to find a customer's identifier before fiche_client. ## Parameters - `segment` (enum, optional) : Starting group, defined as in the app. tous (default); habitues = at least 4 visits in the last 60 days; perdus = came at least 4 times, and absent for more than one and a half times their usual rhythm (median gap between their visits), or, without enough history, for longer than the business's delay; holidays and closing days not counted; presque = only one stamp missing for the reward; recompense_prete = a reward earned, not yet given, not expired; nouveaux = signed up in the last 30 days; inactifs = no visit for longer than the business's delay, in calendar days, whatever their number of visits; un_seul_passage = one visit at most: signed up but never came back. Values : tous, habitues, perdus, presque, recompense_prete, nouveaux, inactifs, un_seul_passage. Default : "tous". - `recherche` (string, ≤ 60 characters, optional) : Part of the first or last name (accents and case ignored). - `tampons_min` (integer, ≥ 0, optional) : At least this many stamps on the current card. - `tampons_max` (integer, ≥ 0, optional) : At most this many stamps on the current card. - `visites_min` (integer, ≥ 0, optional) : At least this many visits since sign-up (welcome stamps excluded). - `visites_max` (integer, ≥ 0, optional) : At most this many visits since sign-up. - `inactif_depuis_jours` (integer, 1…3650, optional) : No visit for at least N calendar days (a customer who never came counts from their sign-up). - `actif_depuis_jours` (integer, 1…3650, optional) : At least one visit in the last N days. - `inscrit_depuis` (string (date), optional) : Signed up on or after this day (YYYY-MM-DD). - `inscrit_avant` (string (date), optional) : Signed up on or before this day (YYYY-MM-DD). - `derniere_visite_depuis` (string (date), optional) : Last visit on or after this day (YYYY-MM-DD). - `derniere_visite_avant` (string (date), optional) : Last visit on or before this day (YYYY-MM-DD). Excludes those who never came. - `wallet` (enum, optional) : Where the customer keeps their card. aucun = web page, no wallet. Values : tous, apple, google, aucun. Default : "tous". - `tri` (enum, optional) : Order of results. Default derniere_visite (most recent first). Values : derniere_visite, visites, visites_90j, tampons, recompenses, inscription, prenom. Default : "derniere_visite". - `ordre` (enum, optional) : auto (default): descending, except prenom (A→Z). Values : auto, asc, desc. Default : "auto". - `limite` (integer, 1…100, optional) : Maximum number of results. Default : 25. - `decalage` (integer, ≥ 0, optional) : Number of results to skip (pagination). Default : 0. ## Example Prompt : « Who are my regulars who stopped coming? » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "chercher_clients", "arguments": { "segment": "perdus", "tri": "visites", "limite": 20 } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/chercher_clients/ --- # fiche_client With account · Read only. The history of ONE customer: sign-up date, wallet used, number of visits and visit rhythm, visits month by month, current card, rewards earned, given and to give, latest dated visits. Find them by their identifier (from chercher_clients) or by their first name, optionally followed by the last name initial ("Maëlle P."); if several customers match, the list of candidates is returned to choose from. Use it for "when did Zoé last come?", "how many times has Louis come?". Never gives an email or phone number. ## Parameters - `id` (string, optional) : Customer identifier (UUID), as returned by chercher_clients. Takes priority over prenom. - `prenom` (string, ≤ 60 characters, optional) : Customer's first name, optionally followed by the initial or last name ("Maëlle P."). Accents and case ignored. - `historique_limite` (integer, 0…200, optional) : Number of recent visits to detail (default 15). Default : 15. - `inclure_offerts` (boolean, optional) : true: the history also shows given stamps (welcome, review), marked as such. Default false: only real visits. Default : false. - `mois` (integer, 1…36, optional) : Number of months in the visits curve (default 12). Default : 12. ## Example Prompt : « When did Zoé last come in? » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "fiche_client", "arguments": { "prenom": "Zoé" } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/fiche_client/ --- # envoyer_message With account · Writes (confirm). Sends a notification to customers' lock screens (through their Apple Wallet or Google Wallet card). Use it to announce something new, an offer, an event, or to bring customers back. The audience can be set (all, regulars, lost, close to the reward, new, reward to collect, inactive) and combined with filters (days without a visit, stamps on the card, visits, sign-up date). Text of 180 characters at most, sent as is. Only one message per day per business. Call first with confirmer=false: the preview gives the exact text, the number of recipients and how many can actually receive it, without sending anything. Only call with confirmer=true after the person's explicit approval: a message sent cannot be taken back. For automatic messages (win-back, birthday, reminders), use regler_automatismes. ## Parameters - `texte` (string, ≤ 180 characters, required) : The exact text of the notification, 180 characters at most. It is preceded by the business name on the phone: no need to repeat it. - `audience` (enum, optional) : Who receives the message. tous = all customers; habitues = 4 or more visits in 60 days; perdus = regulars whose absence exceeds one and a half times their usual rhythm, or the business's delay without enough history (the definition of the Customers screen); presque = only one stamp missing before the reward; nouveaux = signed up in the last 30 days (or since inscrit_depuis); recompense_prete = a reward earned, not yet collected; inactifs = any customer with no visit for inactif_depuis_jours opening days (by default the business's delay), regular or not. Any audience other than tous, or any filter, requires the "targeted notifications" feature. Values : tous, habitues, perdus, presque, nouveaux, recompense_prete, inactifs. Default : "tous". - `inactif_depuis_jours` (integer, 1…730, optional) : Filter: no visit for at least N OPENING days (closed days and holidays don't count). - `actif_depuis_jours` (integer, 1…730, optional) : Filter: at least one visit in the last N opening days. - `tampons_min` (integer, 0…500, optional) : Filter: at least N stamps on the current card. - `tampons_max` (integer, 0…500, optional) : Filter: at most N stamps on the current card. - `passages_min` (integer, 1…10000, optional) : Filter: at least N visits in total since sign-up. - `inscrit_depuis` (string (date), optional) : Filter: signed up on or after this day (YYYY-MM-DD). - `inscrit_avant` (string (date), optional) : Filter: signed up before this day (YYYY-MM-DD, excluded). - `confirmer` (boolean, optional) : false (default): only returns a preview of what would be done, without changing anything. true: carries it out. Always show the preview to the person and wait for their approval before calling again with true. Default : false. ## Example Prompt : « Tell my customers who are close to their reward that we have almond croissants this weekend. » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "envoyer_message", "arguments": { "texte": "Almond croissants this weekend: just one stamp to go before your reward!", "audience": "presque", "confirmer": false } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/envoyer_message/ --- # historique_messages With account · Read only. Lists the messages already sent to customers, from newest to oldest, with their delivery report: received, customers with no screen (unreachable), failures, and still being sent. Also summarizes the automatic messages sent over the period (win-backs for lost customers, birthdays) and says whether a message can still go out today. Use it to know what was sent, check that a send was delivered, or before writing a new one. ## Parameters - `limite` (integer, 1…50, optional) : Maximum number of results. Default : 10. - `decalage` (integer, ≥ 0, optional) : Number of results to skip (pagination). Default : 0. - `depuis` (string (date), optional) : Only sends from this day on (YYYY-MM-DD). Also used as the start of the period for the automatic messages summary (last 30 days by default). - `jusqu_au` (string (date), optional) : Only sends up to and including this day (YYYY-MM-DD). ## Example Prompt : « What did I send my customers this month, and was it delivered? » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "historique_messages", "arguments": { "depuis": "2026-10-01" } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/historique_messages/ --- # regler_automatismes With account · Writes (confirm). Reads and sets the messages StampyWallet sends to customers on its own: the win-back for regulars who stopped coming (when their absence exceeds one and a half times their usual rhythm, or, without enough history, the business's delay, suggested by trade and adjustable), the birthday message, the "one stamp to go" and "reward ready" reminders (and their delay), the quiet hours during which nothing is sent; also shows the Monday summary sent to the business owner. With no change parameter: returns the current state, texts included. With changes: confirmer=false returns a before → after preview without changing anything, confirmer=true saves. Only the fields provided change. For a one-off message, use envoyer_message. ## Parameters - `relance_active` (boolean, optional) : Turns the automatic win-back for lost customers on or off (one per customer per absence). - `delai_relance_jours` (integer, optional) : The business's delay before a regular counts as "lost", in calendar days: one of 14, 21, 28, 35, 42, 60, 90. It is used for customers without enough history and bounds the rhythm of the others (from half to double). To go back to the delay suggested for the trade, put "delai_relance" in effacer. - `texte_relance` (string, ≤ 180 characters, optional) : Win-back text, 180 characters at most. To go back to the default text, put "texte_relance" in effacer. - `anniversaire_active` (boolean, optional) : Turns the birthday message on or off (it only goes out if a text exists and the card asks for the birthday). - `cadeau_anniversaire` (enum, optional) : The birthday gift, valid for 30 days from the day itself: message (the text only), pourcent (a discount on every purchase), un_offert ("buy one, get one free", once), choix (the customer chooses at the counter between the discount and "buy one, get one free"). Gifts already open don't change. Values : message, pourcent, un_offert, choix. - `pourcent_anniversaire` (integer, 1…90, optional) : The birthday discount percentage (pourcent and choix), from 1 to 90. - `texte_anniversaire` (string, ≤ 180 characters, optional) : Text of the birthday message, 180 characters at most, ideally mentioning the gift. Without text, nothing is sent. - `rappel_presque` (boolean, optional) : "One stamp to go" reminder when only one visit is missing. - `rappel_recompense` (boolean, optional) : "Your reward is waiting" reminder when it has been earned. - `delai_rappel_minutes` (integer, optional) : Delay between the visit and the reminder, in minutes: one of 0, 30, 60, 180, 1440 (1440 = the next day). - `heures_calmes_debut` (integer, 0…23, optional) : Start of quiet hours (hour from 0 to 23): no automatic notification arrives after it. - `heures_calmes_fin` (integer, 0…23, optional) : End of quiet hours (hour from 0 to 23): sending resumes from this hour. - `canal_avis` (enum, optional) : Channel for "review request": auto (recommended: an important message goes out by SMS first except to customers who respond to notifications, a minor one as a notification and by SMS only to customers who respond better to SMS), notif, secours (SMS if the notification doesn't get through), sms (SMS first), email (email if the notification doesn't get through). Values : auto, notif, secours, sms, email. - `importance_avis` (enum, optional) : Importance of "review request": importante (goes first in the SMS budget) or petite. Values : importante, petite. - `canal_anniversaire` (enum, optional) : Channel for "birthday": auto (recommended: an important message goes out by SMS first except to customers who respond to notifications, a minor one as a notification and by SMS only to customers who respond better to SMS), notif, secours (SMS if the notification doesn't get through), sms (SMS first), email (email if the notification doesn't get through). Values : auto, notif, secours, sms, email. - `importance_anniversaire` (enum, optional) : Importance of "birthday": importante (goes first in the SMS budget) or petite. Values : importante, petite. - `canal_rappels` (enum, optional) : Channel for "reminders": auto (recommended: an important message goes out by SMS first except to customers who respond to notifications, a minor one as a notification and by SMS only to customers who respond better to SMS), notif, secours (SMS if the notification doesn't get through), sms (SMS first). Values : auto, notif, secours, sms. - `importance_rappels` (enum, optional) : Importance of "reminders": importante (goes first in the SMS budget) or petite. Values : importante, petite. - `canal_relance` (enum, optional) : Channel for "win-back": auto (recommended: an important message goes out by SMS first except to customers who respond to notifications, a minor one as a notification and by SMS only to customers who respond better to SMS), notif, secours (SMS if the notification doesn't get through), sms (SMS first). Values : auto, notif, secours, sms. - `importance_relance` (enum, optional) : Importance of "win-back": importante (goes first in the SMS budget) or petite. Values : importante, petite. - `canal_messages` (enum, optional) : Channel for "business messages": auto (recommended: an important message goes out by SMS first except to customers who respond to notifications, a minor one as a notification and by SMS only to customers who respond better to SMS), notif, secours (SMS if the notification doesn't get through), sms (SMS first). Values : auto, notif, secours, sms. - `importance_messages` (enum, optional) : Importance of "business messages": importante (goes first in the SMS budget) or petite. Values : importante, petite. - `reserve_sms` (integer, 0…100, optional) : Share of the monthly SMS budget kept for important messages, in %: 0, 25, 50, 75, 100. - `effacer` (array, optional) : Texts to clear. texte_relance goes back to the default text; an empty texte_anniversaire effectively stops birthday sends; delai_relance goes back to the delay suggested for the trade. Values : texte_relance, texte_anniversaire, delai_relance. - `confirmer` (boolean, optional) : false (default): only returns a preview of what would be done, without changing anything. true: carries it out. Always show the preview to the person and wait for their approval before calling again with true. Default : false. ## Example Prompt : « Turn on the birthday message with a 10% discount. » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "regler_automatismes", "arguments": { "anniversaire_active": true, "cadeau_anniversaire": "pourcent", "pourcent_anniversaire": 10, "confirmer": false } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/regler_automatismes/ --- # fermetures With account · Writes (confirm). Manages the business's holidays and closing days: list them, add a holiday period, delete one, or set the days closed every week. Important for loyalty: closed days don't count in a customer's absence, so properly declared holidays stop regulars from looking "lost" and getting a win-back message when you reopen. Optionally, prevenir_clients=true with a text also prepares a notification to all customers (same rules as envoyer_message: one message per day, preview required). action=lister (default) changes nothing. To add, delete or set the days: confirmer=false returns a preview, confirmer=true saves (and sends the message if requested). ## Parameters - `action` (enum, optional) : lister: upcoming holidays and those of the last two months, and days closed every week. ajouter: a period (du, au). supprimer: a period by its id (from lister). jours_hebdo: replaces the days closed every week with jours_fermes. Values : lister, ajouter, supprimer, jours_hebdo. Default : "lister". - `du` (string (date), optional) : First closed day (YYYY-MM-DD), to add. - `au` (string (date), optional) : Last closed day, included (YYYY-MM-DD). By default, the same day as du. - `libelle` (string, ≤ 80 characters, optional) : Name of the period, to keep track ("Summer holidays"). - `id` (string, optional) : Identifier of the period to delete (from action=lister). - `jours_fermes` (array, ≤ 7, optional) : For jours_hebdo: the COMPLETE list of days closed every week (empty list = open every day). Values : dimanche, lundi, mardi, mercredi, jeudi, vendredi, samedi. - `prevenir_clients` (boolean, optional) : true: also sends a notification to all customers with texte. Counts as the message of the day. Default : false. - `texte` (string, ≤ 180 characters, optional) : Text of the notification to customers (180 characters at most), if prevenir_clients=true. - `confirmer` (boolean, optional) : false (default): only returns a preview of what would be done, without changing anything. true: carries it out. Always show the preview to the person and wait for their approval before calling again with true. Default : false. ## Example Prompt : « I'm closed from December 24 to 31, let my customers know. » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "fermetures", "arguments": { "action": "ajouter", "du": "2026-12-24", "au": "2026-12-31", "libelle": "Holiday season", "prevenir_clients": true, "texte": "We are closed from December 24 to 31. Happy holidays, see you soon!", "confirmer": false } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/fermetures/ --- # avis_google With account · Writes (confirm) · Growth plan. The business's Google reviews. Every customer gets the same review request (Wallet notification, SMS or email depending on the channel set in regler_automatismes, canal_avis), with the direct link to the Google listing: no rating step in between, no filtering, no reward in exchange for a review (Google's rules). Shows the numbers over 30 days (requests sent, customers who went to Google); and sets up the request: Google listing link, request on or off, moment (first reward or after N visits), delay, text. With no change: read only. With changes: confirmer=false returns a before → after preview, confirmer=true saves. Only the fields provided change. ## Parameters - `lien` (string, ≤ 2000 characters, optional) : "Write a review" link of the Google listing (g.page/r/…/review, search.google.com/local/writereview?placeid=…). Without it, no request is sent. - `retirer_lien` (boolean, optional) : true: removes the listing link (which effectively stops all requests). - `demande_active` (boolean, optional) : Whether to send the review request to customers. - `moment` (enum, optional) : When to ask: recompense = at the first reward; passages = after N visits (see passages). Values : recompense, passages. - `passages` (integer, 1…50, optional) : Number of visits before the request, if moment=passages. - `delai_minutes` (integer, optional) : Delay between the visit and the request, in minutes: 0, 30, 60, 180, 1440 (1440 = the next day). - `texte_demande` (string, ≤ 300 characters, optional) : Text of the request, 300 characters at most. The link to the Google listing is added after it. - `effacer` (array, optional) : Text fields to clear. texte_demande goes back to the default text. Values : texte_demande. - `confirmer` (boolean, optional) : false (default): only returns a preview of what would be done, without changing anything. true: carries it out. Always show the preview to the person and wait for their approval before calling again with true. Default : false. ## Example Prompt : « Ask for a Google review after the 3rd visit, one hour later. » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "avis_google", "arguments": { "demande_active": true, "moment": "passages", "passages": 3, "delai_minutes": 60, "confirmer": false } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/avis_google/ --- # voir_carte With account · Read only. Shows the business's loyalty card as customers see it in Apple Wallet and Google Wallet, with its current rules: mode (tampons, paliers or points), rewards and thresholds, stamps given at sign-up, reward expiry, points settings, style, color, stamp, and the public customer sign-up link. Use it before any change (modifier_carte uses the same setting names), or when the business owner asks what their card looks like or what its rule is. Read only. ## Parameters - `variantes` (boolean, optional) : true: also shows the three palettes of the current collection, to suggest a color change (then pass it as "palette" to modifier_carte). Default : false. - `tampons_poses` (integer, 0…1000000, optional) : Stamps (or points) added on the preview, to show a card in progress (default: welcome stamps + 2). - `qr` (boolean, optional) : true: attaches the customer sign-up QR code (the one on the poster), to try the card on your phone. Default : false. ## Example Prompt : « Show me my card, with the other possible palettes. » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "voir_carte", "arguments": { "variantes": true } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/voir_carte/ --- # modifier_carte With account · Writes (confirm). Changes one or more settings of the business's loyalty card, in a single call: printed name, style (collection, palette or custom color), stamp, reward rule (tampons/paliers/points mode, thresholds, rewards), stamps given at sign-up, reward expiry, in paliers mode the card shape (forme_paliers: tampons or one of the five shapes), and in points mode the name of the points, the card shape (forme_points), the default value of a visit, shortcuts and points per euro. Only the settings given change; read the card first with voir_carte. Call with confirmer=false (default) to show the before/after, then with confirmer=true after explicit approval. A rule change also applies to cards customers already have. Images (logo, background, image stamp) are changed in the app, Card page. ## Parameters - `nom` (string, ≤ 80 characters, optional) : Business name, printed at the top of the card. - `mode` (enum, optional) : tampons: one reward at one threshold (the simplest). paliers: several rewards on the same card (e.g. 5 and 10). points: the business enters a number of points at each visit (varying baskets). Values : tampons, paliers, points. - `recompense` (string, ≤ 120 characters, optional) : The reward (tampons mode). Ignored if "paliers" is given. - `tampons` (integer, 2…30, optional) : Number of visits to earn the reward (tampons mode), from 2 to 30. - `paliers` (array, ≤ 6, optional) : The rewards and their thresholds (paliers and points modes). From smallest to largest. - `paliers[].a` (integer, 1…1000000, required) : Threshold: number of stamps (or points in points mode) to reach this tier. - `paliers[].recompense` (string, ≤ 120 characters, required) : What the customer gets, as printed on the card. E.g. "A free coffee". - `paliers[].icone` (enum, optional) : Icon for the tier (default: cadeau). Values : etincelle, eclat, goutte, main, pinceau, oeil, ciseaux, sechoir, fleur, lotus, coeur, etoile, diamant, couronne, soleil, lune, talon, robe, sac, serviette, cafe, grain, the, cookie, gateau, pain, part, burger, glace, bol, plat, verre, biere, bulles, cocktail, cerises, orange, avocat, piment, oeuf, poisson, fromage, popcorn, feuille, flamme, eclair, sourire, patte, chien, chat, cadeau, haltere, velo, livre, note, appareil, palette, dent, parapluie, ballon, tshirt, basket, croissant. - `collection` (enum, optional) : Card style: original (simple), cafe (thin frame), botanique (italic, foliage), trattoria (scalloped), atelier (modern), pass (like an Apple Wallet pass: large logo, stamps as shaded objects, solid background). Values : original, cafe, botanique, trattoria, atelier, pass. - `palette` (integer, 0…3, optional) : Palette of the collection: 0, 1 or 2 (each collection offers three; pass offers a fourth, 3). Ignored if "couleur" is given. - `couleur` (string, optional) : Custom background color, in hexadecimal. The ink (text) color is chosen automatically to stay readable. - `tampon` (enum, optional) : The mark added at each visit. "rond": the simple dot. Beauty: etincelle, eclat, goutte, main, pinceau, oeil, ciseaux, sechoir, fleur, lotus, coeur, etoile, diamant, couronne, soleil, lune, talon, robe, sac, serviette. Food: cafe, grain, the, cookie, gateau, pain, part, burger, glace, bol, plat, verre, biere, bulles, cocktail, cerises, orange, avocat, piment, oeuf, poisson, fromage, popcorn. Other: feuille, flamme, eclair, sourire, patte, chien, chat, cadeau, haltere, velo, livre, note, appareil, palette, dent, parapluie, ballon, tshirt, basket. Values : rond, etincelle, eclat, goutte, main, pinceau, oeil, ciseaux, sechoir, fleur, lotus, coeur, etoile, diamant, couronne, soleil, lune, talon, robe, sac, serviette, cafe, grain, the, cookie, gateau, pain, part, burger, glace, bol, plat, verre, biere, bulles, cocktail, cerises, orange, avocat, piment, oeuf, poisson, fromage, popcorn, feuille, flamme, eclair, sourire, patte, chien, chat, cadeau, haltere, velo, livre, note, appareil, palette, dent, parapluie, ballon, tshirt, basket, croissant. - `tampons_bienvenue` (integer, 0…5, optional) : Stamps given when the customer signs up (0 to 5). 1 by default: a card already started makes people want to finish it. - `nom_des_points` (string, ≤ 20 characters, optional) : Points mode: what the points are called ("points", "seeds", "stars"…). - `forme_paliers` (enum, optional) : Paliers mode: the card design. tampons (default): stamps in a grid, each reward in place of the stamp at its threshold. Or one of the five points shapes, which then counts stamps: medailles (a gauge and a medal per reward), vitrine (rewards side by side, each with its gauge), remplissage (the next reward shown large, filling with color), chemin (an S-shaped road where each stamp moves the marker forward), anneau (a round gauge around the next reward). Reward photos are uploaded in the app, Card page. Values : tampons, medailles, vitrine, remplissage, chemin, anneau. - `forme_points` (enum, optional) : Points mode: the card design. medailles (default): a thick gauge and a large medal per reward. vitrine: rewards side by side, each with a small gauge. remplissage: the next reward shown large, filling with color as it goes. chemin: an S-shaped road, with rewards as steps. anneau: a round gauge around the next reward, with the list next to it. Reward photos are uploaded in the app, Card page. Values : medailles, vitrine, remplissage, chemin, anneau. - `expiration_recompense_jours` (integer, 0…365, optional) : Days an earned reward stays valid: 0, 30, 60, 90, 180, 365 (0 = never expires). - `points_par_defaut` (integer, 1…100000, optional) : Points mode: points added by default at each visit (1 to 100,000). - `points_raccourcis` (array, ≤ 12, optional) : Points mode: the values offered with one tap when scanning (up to 6, sorted and deduplicated). E.g. [1, 2, 5, 10]. - `points_par_euro` (number, 0.01…10000, optional) : Points mode: points per euro spent, a guide shown to the business (e.g. 1 or 0.5). - `confirmer` (boolean, optional) : false (default): only returns a preview of what would be done, without changing anything. true: carries it out. Always show the preview to the person and wait for their approval before calling again with true. Default : false. ## Example Prompt : « Switch my card to tiers: a coffee at 5 visits, a croissant at 10. » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "modifier_carte", "arguments": { "mode": "paliers", "paliers": [ { "a": 5, "recompense": "A free coffee", "icone": "cafe" }, { "a": 10, "recompense": "A free croissant", "icone": "croissant" } ], "confirmer": false } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/modifier_carte/ --- # fiche_commerce With account · Writes (confirm). Reads or changes the business profile: name, type of business, average basket (used for the dashboard estimates), customers' language, details printed on the back of the card (address, phone, opening hours, website, welcome message) and the public customer sign-up link (stampywallet.com/rejoindre/, the one in the QR code). With no change parameter: returns the current profile. With changes: confirmer=false (default) shows the before/after, confirmer=true writes after explicit approval. An old public link keeps working after a change (QR codes already printed stay valid). ## Parameters - `nom` (string, ≤ 80 characters, optional) : Business name (also printed on the card). - `metier` (enum, optional) : Type of business. "autre": beauty salons, nails, florists, hairdressers, grocery stores… Values : cafe, boulangerie, restaurant, autre. - `panier_moyen` (number, 0.01…100000, optional) : Average spend per customer per visit, in euros. - `langue` (enum, optional) : The business's language: that of the card, pages and messages customers see. Values : fr, en, es. - `adresse` (string, ≤ 160 characters, optional) : Address, on the back of the card (160 characters at most). - `telephone` (string, ≤ 30 characters, optional) : Phone, on the back of the card. - `horaires` (string, ≤ 120 characters, optional) : Opening hours, as free text (e.g. "Tue–Sat 7am–7pm"). - `site_web` (string, ≤ 160 characters, optional) : Website or Instagram page. - `message_bienvenue` (string, ≤ 180 characters, optional) : Short note on the back of the card, read by the customer when adding it. - `lien` (string, ≤ 120 characters, optional) : Desired public link, in plain words ("Le Fournil de la Gare" becomes "le-fournil-de-la-gare"), from 3 to 32 characters. A link already taken by another business is refused. - `effacer` (array, ≤ 6, optional) : Fields to clear. "lien": the public address goes back to the technical identifier (the link stays reserved for this business). Values : adresse, telephone, horaires, site_web, message_bienvenue, lien. - `confirmer` (boolean, optional) : false (default): only returns a preview of what would be done, without changing anything. true: carries it out. Always show the preview to the person and wait for their approval before calling again with true. Default : false. ## Example Prompt : « Add my opening hours to the back of the card: Tuesday to Saturday, 7am to 7pm. » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "fiche_commerce", "arguments": { "horaires": "Tue–Sat 7am–7pm", "confirmer": false } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/fiche_commerce/ --- # affichette With account · Read only. Returns the customer sign-up QR code (the one to put on the counter, which customers scan to add the card to their phone), the public link, the card, and download links for the poster to print (A5 or A6 PDF) and for the QR code alone (PNG, SVG) for a menu, a shop window or Canva. Use it when the business owner wants to print their poster, get their QR code or the link to put in their bio. Downloads open in the app, signed in. To receive the PDFs by email: envoyer_affichette. Read only. ## Parameters - `format` (enum, optional) : PDF format highlighted: a5 (half sheet, counter poster) or a6 (quarter sheet, table stand or card holder). Values : a5, a6. Default : "a5". ## Example Prompt : « Give me my QR code and the poster to print. » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "affichette", "arguments": { "format": "a5" } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/affichette/ --- # envoyer_affichette With account · Writes (confirm). Sends the counter poster (A5 and A6 PDFs, with the customer sign-up QR code) to the email address of the connected StampyWallet account, to print it from another device or at a print shop. Only to that address, never to another. Call with confirmer=false (default) to announce the send, then confirmer=true after approval. ## Parameters - `confirmer` (boolean, optional) : false (default): only returns a preview of what would be done, without changing anything. true: carries it out. Always show the preview to the person and wait for their approval before calling again with true. Default : false. ## Example Prompt : « Email me the poster so I can print it. » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "envoyer_affichette", "arguments": { "confirmer": false } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/envoyer_affichette/ --- # equipe With account · Writes (confirm) · Owner only. Manages the business's team (owner only). action=lister: members (name, email, role, stamps added) and pending invitations. action=inviter: creates a personal invitation valid for 14 days (staff role = scanner only, or manager = numbers, customers, messages, card); with "email", it is sent to the person by email, otherwise the link is returned to be passed on. action=changer_role: switches a member to staff or manager. action=retirer: removes a member's access and closes their sessions. action=annuler_invitation: cancels a link not yet used. Changes take confirmer=false (default, preview) then confirmer=true after explicit approval. Making someone an owner is done in the app. ## Parameters - `action` (enum, optional) : What to do. Values : lister, inviter, changer_role, retirer, annuler_invitation. Default : "lister". - `membre_id` (string, ≤ 36 characters, optional) : changer_role, retirer: the member's identifier, read with action=lister. - `email` (string, ≤ 254 characters, optional) : inviter: address of the invited person, the invitation is sent to them by email. changer_role, retirer: email of a member (instead of membre_id). - `nom` (string, ≤ 60 characters, optional) : inviter: a label to keep track of invitations (e.g. "Julie, mornings"). - `role` (enum, optional) : inviter (default staff), changer_role: staff = staff member, only sees the scanner; manager = sees numbers, customers, messages and sets up the card, without touching the team. Values : staff, manager. - `invitation` (string, ≤ 300 characters, optional) : annuler_invitation: the invitation link or its token, read with action=lister. - `confirmer` (boolean, optional) : false (default): only returns a preview of what would be done, without changing anything. true: carries it out. Always show the preview to the person and wait for their approval before calling again with true. Default : false. ## Example Prompt : « Invite Julie as staff, her email is julie@example.com. » ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "equipe", "arguments": { "action": "inviter", "email": "julie@example.com", "nom": "Julie, mornings", "role": "staff", "confirmer": false } } } ``` Source : https://stampywallet.com/en/docs/mcp/tools/equipe/ --- # The StampyWallet API Add visits, create customers and receive your loyalty card's events from your POS, your website or a no-code tool. The API connects StampyWallet to the rest of your business. It works both ways: - **Into StampyWallet**: your POS, your website or your booking software adds a visit, creates a customer or gives a reward, without scanning at the counter. - **Out of StampyWallet**: on every visit, new card or reward, StampyWallet calls an address of your choice (a [webhook](https://stampywallet.com/en/docs/api/webhooks/)). That is what feeds a spreadsheet, a CRM or a Zapier scenario. > **Before you start :** the API, the POS incoming addresses ([Connect your POS](https://stampywallet.com/en/docs/api/connect-your-pos/)) and webhooks are included in the **Growth** plan (€40 excl. VAT per month), and open during the free trial. On the Essential plan, calls respond `403` with the code `formule_requise`. > **Warning :** the API is in **beta**. The routes and fields described here are those of the implementation; new fields may appear before the stable version: ignore the ones you don't know. Changes are announced in the [changelog](site:changelog/). ## Base URL All routes are served over HTTPS, in JSON (field names in French, in snake_case), under: ```text title="Base URL" https://app.stampywallet.com/v1 ``` Identifiers are UUIDs; amounts are in the business's currency, in units (`12.5` for €12.50). ## A first call Create a key in **Settings › Integrations** (see [Authentication](https://stampywallet.com/en/docs/api/authentication/)), then read your business profile: ```bash title="curl" curl https://app.stampywallet.com/v1/commerce \ -H "Authorization: Bearer sw_live_your_key" ``` ```js title="Node.js" const reponse = await fetch('https://app.stampywallet.com/v1/commerce', { headers: { Authorization: `Bearer ${process.env.STAMPY_CLE}` }, }); console.log(await reponse.json()); ``` ```python title="Python" import os, requests reponse = requests.get( "https://app.stampywallet.com/v1/commerce", headers={"Authorization": f"Bearer {os.environ['STAMPY_CLE']}"}, ) print(reponse.json()) ``` ## What the API can do | Route | Purpose | |---|---| | `GET /v1/commerce` | [Read the business profile](https://stampywallet.com/en/docs/api/shop/) and the card rules | | `POST /v1/clients` | [Create a customer](https://stampywallet.com/en/docs/api/create-customer/) and get their card link | | `GET /v1/clients` | [Find a customer](https://stampywallet.com/en/docs/api/find-customers/) by phone, email or card | | `GET /v1/clients/:id` | [Get a customer](https://stampywallet.com/en/docs/api/get-customer/) and the state of their card | | `POST /v1/passages` | [Add a visit](https://stampywallet.com/en/docs/api/add-visit/): a stamp, or points based on the amount | | `DELETE /v1/passages/:id` | [Cancel a visit](https://stampywallet.com/en/docs/api/cancel-visit/) | | `POST /v1/recompenses` | [Give an earned reward](https://stampywallet.com/en/docs/api/give-reward/) | | `POST /v1/entrants/:jeton` | [Receive sales from your POS](https://stampywallet.com/en/docs/api/incoming/) (Square, Shopify, SumUp, Zettle, or the generic format) | And the other way, [outgoing webhooks](https://stampywallet.com/en/docs/api/webhooks/): nine signed [events](https://stampywallet.com/en/docs/api/events/). > **Tip :** no developer at hand? The [Connect your POS](https://stampywallet.com/en/docs/api/connect-your-pos/) page gives step-by-step settings for Square, Shopify, SumUp and Zettle, and for Zapier, Make and n8n. ## What about MCP? The API is made for software: a POS, a website, a script. To run your business **in a conversation**, from Claude, ChatGPT or Cursor, use the [MCP server](https://stampywallet.com/en/docs/mcp/connect/) instead: it needs no key, only connecting your account. ## Frequently asked questions **How much does the API cost?** The API, POS incoming addresses and webhooks are included in the Growth plan, at €40 excl. VAT per month, and open during the 30-day free trial. On the Essential plan, calls respond `403`. **Do I need a developer to connect my POS?** Not for Square, Shopify, SumUp, Zettle, Zapier, Make or n8n: the [Connect your POS](https://stampywallet.com/en/docs/api/connect-your-pos/) page gives step-by-step settings. Calling the API from your own software, however, takes someone who writes code. **Is there a rate limit?** Yes: 120 requests per minute per key. Beyond that, the response is `429` with a `Retry-After` header. **Does one key work for several locations?** No. A key gives access only to the business it was created in: create a key in each location. **How do I avoid a double stamp when my POS retries?** Give each visit a `reference`, such as the receipt number, and send an `Idempotency-Key` header on every `POST`: a retry does not stamp twice. Source : https://stampywallet.com/en/docs/api/ --- # Authentication Every API call carries a secret sw_live_ key, created in Settings › Integrations and shown only once. 120 requests per minute per key. ## Create a key 1. **Open Settings › Integrations** in your StampyWallet account (the owner or a manager). 2. **Create a key** and give it a name that says where it is used ("Counter POS", "Website"). 3. **Copy it right away.** It starts with `sw_live_` and is shown in full only once: StampyWallet only keeps a fingerprint of it. If lost, it cannot be recovered: revoke it and create another one. ## Send it with every call The key goes in the `Authorization` header, after the word `Bearer`: ```http title="Header" Authorization: Bearer sw_live_4f9c2a7e1b... ``` A key gives access to **one** business: the one it was created in. If you manage several locations, create a key in each one. > **Warning :** the key acts on your behalf: it creates customers and adds visits. Keep it on the server side (environment variable, secrets vault), never in the code of a web page or a mobile app, never in a Git repository. ## Revoke a key In **Settings › Integrations**, revoke the key: calls carrying it fail immediately with `401` and the code `cle_revoquee`. Do it as soon as a key may have leaked, or when a provider no longer needs it. ## Access errors | Status | `code` | When | |---|---|---| | `401` | `non_authentifie` | Missing header or unknown key | | `401` | `cle_revoquee` | The key has been revoked | | `403` | `formule_requise` | The business is not on the Growth plan (the API is included in it, and open during the trial) | | `403` | `interdit` | The action is not allowed for this business | | `429` | `trop_de_requetes` | More than 120 requests per minute with this key | ## Rate limit **120 requests per minute per key.** Beyond that, the response is `429` with the header `Retry-After: 60`: wait that number of seconds before trying again. An ordinary POS is very far from it. The error format in detail is on the [Errors](https://stampywallet.com/en/docs/api/errors/) page. Source : https://stampywallet.com/en/docs/api/authentication/ --- # Errors and retries The API error format, all error codes, and the Idempotency-Key header to retry a POST with no risk of duplicates. ## Error format An error responds with an HTTP 4xx or 5xx status and always the same body: ```json title="Error response" { "erreur": { "code": "client_introuvable", "message": "No customer matches in this business." } } ``` - `code` is stable: it is what your program should test. - `message` is a readable sentence, for your logs. Its text may change. ## Codes | Status | Codes | |---|---| | `400` | `requete_invalide` (unreadable JSON), `client_requis`, `recherche_requise`, `montant_invalide`, `quantite_invalide`, `points_invalide`, `email_invalide`, `telephone_invalide`, `idempotency_key_invalide` | | `401` | `non_authentifie`, `cle_revoquee`, `signature_invalide` (incoming addresses) | | `403` | `formule_requise` (the API requires the Growth plan), `interdit` | | `404` | `introuvable` (address), `client_introuvable`, `passage_introuvable` | | `409` | `trop_tot`, `pas_dernier`, `deja_annule`, `recompense_offerte`, `aucune_recompense`, `requete_en_cours` | | `422` | `conditions_requises`, `montant_insuffisant` | | `429` | `trop_de_requetes` (see [the rate limit](https://stampywallet.com/en/docs/api/authentication/#limite)) | | `500` | `interne`: try again later, with the same `Idempotency-Key` | ## Retry without duplicates: Idempotency-Key The network drops at the wrong moment, and you don't know whether the visit was recorded. To retry safely, send an `Idempotency-Key` header on every `POST` (`/clients`, `/passages`, `/recompenses`): a unique value per operation, from 1 to 200 characters, for example the receipt number. ```bash title="curl" curl https://app.stampywallet.com/v1/passages \ -H "Authorization: Bearer sw_live_your_key" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: ticket-2026-10-25-0042" \ -d '{"client": {"telephone": "+33612345678"}, "montant": 12.50}' ``` - The response is kept for **24 hours**. The same key replayed returns the same status and the same body, with the header `Idempotent-Replayed: true`. - A `4xx` error is kept too: replaying it returns the same error. A `500` error releases the key, so you can try again. - While the first request is still running: `409 requete_en_cours`. > **Tip :** on [`POST /v1/passages`](https://stampywallet.com/en/docs/api/add-visit/), the `reference` field (your receipt number) also protects you: the same reference only adds a stamp once per business, even without `Idempotency-Key`. Source : https://stampywallet.com/en/docs/api/errors/ --- # Get the business GET /v1/commerce returns the profile of the business linked to the key and the rules of its loyalty card. `GET /v1/commerce` Returns the business the key belongs to: its name, currency, time zone and card rules (mode, goal, rewards, amount rule). Handy to check a key, or to show the card rule in your POS. ## Example ```bash title="curl" curl https://app.stampywallet.com/v1/commerce \ -H "Authorization: Bearer sw_live_your_key" ``` ```js title="Node.js" const r = await fetch('https://app.stampywallet.com/v1/commerce', { headers: { Authorization: `Bearer ${process.env.STAMPY_CLE}` }, }); const commerce = await r.json(); ``` ## Response ```json title="200 OK" { "id": "5f0c8e2a-3b1d-4c9e-9a51-2d7f3e8b6a10", "nom": "Boulangerie Lemaire", "mode": "tampons", "tampons_requis": 10, "recompense": "A free baguette", "recompenses": [ { "seuil": 10, "libelle": "A free baguette" } ], "devise": "EUR", "montant_par_tampon": 10, "points_par_unite": null, "tampons_bienvenue": 1, "conditions_exigees": false, "premium": null, "fuseau": "Europe/Paris", "langue": "fr" } ``` | Field | Meaning | |---|---| | `mode` | `tampons`, `paliers` or `points` ([card types](https://stampywallet.com/en/docs/guide/card-types/)) | | `recompenses` | Each reward with its `seuil` (threshold) | | `montant_par_tampon` | The "€10 = 1 stamp" rule, or `null` | | `points_par_unite` | In points mode: how many points for 1 unit of currency | | `conditions_exigees` | `true` if the business requires acceptance of its terms: then pass `conditions_acceptees: true` when creating a customer | | `premium` | The second counter, when the business has opened it, otherwise `null` | Source : https://stampywallet.com/en/docs/api/shop/ --- # Create a customer POST /v1/clients signs a customer up to the loyalty program and returns their card link, to send them by SMS or email. `POST /v1/clients` Signs a customer up, as if they had scanned the poster, and returns the customer object with `lien_carte`: the page where they add their card to Apple Wallet or Google Wallet. The business's welcome stamps are added, as at sign-up. **Body (JSON)** - `prenom` (string, optional) : Customer's first name, shown on their card. - `nom` (string, optional) : Last name. - `telephone` (string, optional) : A mobile number, in the business's country format or international (`+33612345678`). Otherwise `400 telephone_invalide`. - `email` (string, optional) : Email address. - `conditions_acceptees` (boolean, optional) : Required (`true`) when the business requires its terms (`conditions_exigees` from [`GET /v1/commerce`](https://stampywallet.com/en/docs/api/shop/)); otherwise `422 conditions_requises`. > **Warning :** this route **does not deduplicate**: called twice, it creates two customers. Search first with [`GET /v1/clients`](https://stampywallet.com/en/docs/api/find-customers/), or go straight through [`POST /v1/passages`](https://stampywallet.com/en/docs/api/add-visit/) with `creer_si_absent: true`, which only creates the customer if they don't exist. A customer created through the API is never subscribed to promotional SMS: they have not been informed about them. ## Example ```bash title="curl" curl https://app.stampywallet.com/v1/clients \ -H "Authorization: Bearer sw_live_your_key" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: inscription-0412" \ -d '{"prenom": "Léa", "telephone": "+33612345678"}' ``` ```js title="Node.js" const r = await fetch('https://app.stampywallet.com/v1/clients', { method: 'POST', headers: { Authorization: `Bearer ${process.env.STAMPY_CLE}`, 'Content-Type': 'application/json', 'Idempotency-Key': 'inscription-0412', }, body: JSON.stringify({ prenom: 'Léa', telephone: '+33612345678' }), }); const client = await r.json(); console.log(client.lien_carte); ``` ```python title="Python" import os, requests r = requests.post( "https://app.stampywallet.com/v1/clients", headers={"Authorization": f"Bearer {os.environ['STAMPY_CLE']}", "Idempotency-Key": "inscription-0412"}, json={"prenom": "Léa", "telephone": "+33612345678"}, ) lien = r.json()["lien_carte"] ``` ## Response `201 Created` and [the customer object](https://stampywallet.com/en/docs/api/get-customer/#objet), for example: ```json title="201 Created" { "id": "c2a91f3e-7d40-4b8a-a3c5-91e0d6f4b2c7", "prenom": "Léa", "nom": null, "telephone": "+33612345678", "email": null, "carte": "X7k2pQ9mLw", "lien_carte": "https://stampywallet.com/c/X7k2pQ9mLw", "cree_le": "2026-10-25T10:41:52.120Z", "etat": { "mode": "tampons", "tampons": 1, "points": null, "objectif": 10, "restants": 9, "prochaine_recompense": { "seuil": 10, "libelle": "A free baguette" }, "recompenses_disponibles": [], "recompenses_obtenues": 0, "dernier_passage": null, "premium": null } } ``` > **Note :** send `lien_carte` to the customer through the channel of your choice: StampyWallet does not message them at this step. Source : https://stampywallet.com/en/docs/api/create-customer/ --- # Find a customer GET /v1/clients finds a customer by their phone, email or card code. `GET /v1/clients` Finds a customer of the business. Give **only one** of the three query parameters (otherwise `400 recherche_requise`): **Query parameters** - `telephone` (string, optional) : The number, URL-encoded (`%2B33612345678`). It is read in the business's country: `0612345678` and `+33612345678` both match. - `email` (string, optional) : Email address, case-insensitive. - `carte` (string, optional) : The card code, the one in its QR code. ## Example ```bash title="curl" curl "https://app.stampywallet.com/v1/clients?telephone=%2B33612345678" \ -H "Authorization: Bearer sw_live_your_key" ``` ## Response ```json title="200 OK" { "clients": [ { "id": "c2a91f3e-7d40-4b8a-a3c5-91e0d6f4b2c7", "prenom": "Léa", "nom": "Martin", "telephone": "+33612345678", "email": null, "carte": "X7k2pQ9mLw", "lien_carte": "https://stampywallet.com/c/X7k2pQ9mLw", "cree_le": "2026-10-25T10:41:52.120Z", "etat": { "mode": "tampons", "tampons": 7, "points": null, "objectif": 10, "restants": 3, "prochaine_recompense": { "seuil": 10, "libelle": "A free baguette" }, "recompenses_disponibles": [], "recompenses_obtenues": 1, "dernier_passage": "2026-10-28T08:02:11.000Z", "premium": null } } ] } ``` The list is empty (`"clients": []`) when nobody matches: this is not an error. When several profiles share the same email or number, the one who came most recently is returned. The fields in detail: [the customer object](https://stampywallet.com/en/docs/api/get-customer/#objet). Source : https://stampywallet.com/en/docs/api/find-customers/ --- # Get a customer GET /v1/clients/:id returns a customer and the state of their loyalty card, stamps, points and rewards to give. `GET /v1/clients/:id` Returns a customer and the state of their card. `:id` is the UUID returned by [`POST /v1/clients`](https://stampywallet.com/en/docs/api/create-customer/) or [`GET /v1/clients`](https://stampywallet.com/en/docs/api/find-customers/). An unknown identifier responds `404 client_introuvable`. ```bash title="curl" curl https://app.stampywallet.com/v1/clients/c2a91f3e-7d40-4b8a-a3c5-91e0d6f4b2c7 \ -H "Authorization: Bearer sw_live_your_key" ``` ## The customer object The same object comes back in every customer route, and in the `client` field of other responses: ```json title="200 OK" { "id": "c2a91f3e-7d40-4b8a-a3c5-91e0d6f4b2c7", "prenom": "Léa", "nom": "Martin", "telephone": "0612345678", "email": "lea@exemple.fr", "carte": "X7k2pQ9mLw", "lien_carte": "https://stampywallet.com/c/X7k2pQ9mLw", "cree_le": "2026-10-22T09:14:03.120Z", "etat": { "mode": "tampons", "tampons": 3, "points": null, "objectif": 10, "restants": 7, "prochaine_recompense": { "seuil": 10, "libelle": "A free coffee" }, "recompenses_disponibles": [ { "id": "504c8734-3bbd-4fe9-95f8-7a67c8239dab", "libelle": "A free coffee", "seuil": 10, "piste": "normal", "obtenue_le": "2026-10-20T16:03:44.000Z", "expire_le": null } ], "recompenses_obtenues": 1, "dernier_passage": "2026-10-22T09:20:00.000Z", "premium": null } } ``` | Field | Meaning | |---|---| | `carte` | The card code, the one in its QR code | | `lien_carte` | The card page, which offers to add it to Apple Wallet or Google Wallet | | `etat.tampons`, `etat.points` | `tampons` in stamps and tiers modes, `points` in points mode; the other one is `null` | | `etat.restants` | What is missing before the next reward | | `etat.recompenses_disponibles` | Rewards earned, not yet given: their `id` is used to [give them](https://stampywallet.com/en/docs/api/give-reward/) | | `etat.premium` | `{ nom, tampons, objectif, restants }` when the business has opened the second counter, otherwise `null` | > **Note :** identifiers are UUIDs. `cli_`, `pas_` or `rec_` prefixes in front of a UUID are accepted as input, and ignored. Source : https://stampywallet.com/en/docs/api/get-customer/ --- # Add a visit POST /v1/passages adds a stamp or points to a customer's card from your POS, and tells you whether a reward is earned. `POST /v1/passages` The equivalent of a scan at the counter, sent by your POS: one stamp (or several), or points. The rules and limits are those of the counter, the customer's card updates in their Wallet, and the visit counts like any other (regulars, reminders, review request). **Body (JSON)** - `client` (object, required) : Who gets the visit: **one** of `id`, `carte` (the card code), `email` or `telephone`. - `montant` (number, optional) : Receipt amount, in units (`12.5` for €12.50). If the business has a "€10 = 1 stamp" rule, the amount takes precedence over `quantite`; in points mode, it is converted with the business's points-per-euro rule. - `quantite` (integer, optional) : Stamps and tiers modes: number of stamps, 1 by default, 50 at most. - `points` (integer, optional) : Points mode, without an amount: the points to add. By default, the business's value. - `reference` (string, optional) : The receipt number. A reference only adds a stamp once per business. - `creer_si_absent` (boolean, optional) : `true`: an unknown customer, identified by `email` or `telephone`, is created (with `prenom` and `nom` if they are in `client`). Default `false`. - `conditions_acceptees` (boolean, optional) : When the business requires its terms and `creer_si_absent` creates the customer (in `client` or at the root). ## Example ```bash title="curl" curl https://app.stampywallet.com/v1/passages \ -H "Authorization: Bearer sw_live_your_key" \ -H "Content-Type: application/json" \ -d '{ "client": { "telephone": "+33612345678", "prenom": "Léa" }, "montant": 12.5, "reference": "TICKET-4512", "creer_si_absent": true }' ``` ```js title="Node.js" const r = await fetch('https://app.stampywallet.com/v1/passages', { method: 'POST', headers: { Authorization: `Bearer ${process.env.STAMPY_CLE}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ client: { telephone: '+33612345678', prenom: 'Léa' }, montant: 12.5, reference: 'TICKET-4512', creer_si_absent: true, }), }); const resultat = await r.json(); if (resultat.recompense_atteinte) console.log('Reward to give!'); ``` ```python title="Python" import os, requests r = requests.post( "https://app.stampywallet.com/v1/passages", headers={"Authorization": f"Bearer {os.environ['STAMPY_CLE']}"}, json={"client": {"telephone": "+33612345678", "prenom": "Léa"}, "montant": 12.5, "reference": "TICKET-4512", "creer_si_absent": True}, ) print(r.json()["recompense_atteinte"]) ``` ## Response ```json title="201 Created" { "passage": { "id": "226fdea1-ba59-49fb-93e6-60a14a765134", "client_id": "c2a91f3e-7d40-4b8a-a3c5-91e0d6f4b2c7", "valeur": 1, "montant": 12.5, "reference": "TICKET-4512", "cree_le": "2026-10-22T09:20:00.000Z" }, "rejoue": false, "client_cree": false, "recompense_atteinte": true, "recompenses_gagnees": 1, "plafonne": false, "client": { "id": "c2a91f3e-7d40-4b8a-a3c5-91e0d6f4b2c7", "prenom": "Léa", "etat": { "tampons": 0, "objectif": 10 } } } ``` | Field | Meaning | |---|---| | `passage.valeur` | The stamps or points added | | `passage.montant` | The recorded amount, or `null` | | `rejoue` | `true` when the `reference` had already been used: the response is then `200` and returns the original visit, without adding a stamp a second time | | `client_cree` | `true` if `creer_si_absent` created the customer | | `recompense_atteinte`, `recompenses_gagnees` | This visit earns a reward (or several): to [give](https://stampywallet.com/en/docs/api/give-reward/) when the customer takes it | | `plafonne` | `true` if the value was brought down to the counter's cap | | `client` | [The customer object](https://stampywallet.com/en/docs/api/get-customer/#objet) after the visit | ## Refusals | Status | `code` | When | |---|---|---| | `404` | `client_introuvable` | No customer matches, and `creer_si_absent` is not `true` | | `409` | `trop_tot` | A visit from the same customer happened less than the business's double-visit delay ago (90 seconds by default) | | `422` | `montant_insuffisant` | The amount does not make one stamp (or one point) | | `422` | `conditions_requises` | The customer would be created, but the business's terms are not accepted | > **Warning :** always give a `reference` (the receipt number): a POS that retries after a connection drop does not add two stamps. A reference whose visit was cancelled cannot be used again. Source : https://stampywallet.com/en/docs/api/add-visit/ --- # Cancel a visit DELETE /v1/passages/:id removes a customer's last visit, for example after a receipt is voided or refunded at the register. `DELETE /v1/passages/:id` Removes a visit, with a manager's rights: the stamps or points it added are taken off, and a reward it had unlocked disappears with it. `:id` is the UUID returned by [`POST /v1/passages`](https://stampywallet.com/en/docs/api/add-visit/). Optional body: `{ "motif": "Voided receipt" }` (or `?motif=` in the address). By default, the reason is "API". ## Example ```bash title="curl" curl -X DELETE https://app.stampywallet.com/v1/passages/226fdea1-ba59-49fb-93e6-60a14a765134 \ -H "Authorization: Bearer sw_live_your_key" \ -H "Content-Type: application/json" \ -d '{"motif": "Refunded receipt"}' ``` ## Response ```json title="200 OK" { "annule": true, "recompenses_retirees": 1, "client": { "id": "c2a91f3e-7d40-4b8a-a3c5-91e0d6f4b2c7", "prenom": "Léa", "etat": { "tampons": 9, "objectif": 10 } } } ``` ## Refusals As at the counter, only a customer's **last** visit can be cancelled, and never twice. | Status | `code` | When | |---|---|---| | `404` | `passage_introuvable` | The visit does not exist in this business | | `409` | `pas_dernier` | It is not the customer's last visit | | `409` | `deja_annule` | The visit is already cancelled | | `409` | `recompense_offerte` | The reward unlocked by this visit has already been given (the response carries a `recompense` field) | Source : https://stampywallet.com/en/docs/api/cancel-visit/ --- # Give a reward POST /v1/recompenses marks a reward earned by a customer as given, from your POS. `POST /v1/recompenses` Marks an earned reward as handed to the customer, the equivalent of the scanner's "Given" button. The customer's card updates in their Wallet. **Body (JSON)** - `client` (object, required) : The customer: **one** of `id`, `carte`, `email` or `telephone`. - `recompense_id` (string, optional) : The UUID of the reward to give (in `etat.recompenses_disponibles` of [the customer object](https://stampywallet.com/en/docs/api/get-customer/#objet)). By default: the oldest one due. - `piste` (string, optional) : `normal` or `premium` (the second counter): without `recompense_id`, the oldest one due on that track. ## Example ```bash title="curl" curl https://app.stampywallet.com/v1/recompenses \ -H "Authorization: Bearer sw_live_your_key" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: recompense-ticket-0057" \ -d '{"client": {"carte": "X7k2pQ9mLw"}}' ``` ## Response ```json title="200 OK" { "recompense": { "id": "504c8734-3bbd-4fe9-95f8-7a67c8239dab", "libelle": "A free coffee", "piste": "normal", "offerte_le": "2026-10-22T09:25:00.000Z" }, "client": { "id": "c2a91f3e-7d40-4b8a-a3c5-91e0d6f4b2c7", "prenom": "Léa", "etat": { "recompenses_disponibles": [] } } } ``` With no reward to give, the response is `409` with the code `aucune_recompense`. Source : https://stampywallet.com/en/docs/api/give-reward/ --- # Webhooks Receive a signed HTTP call on your server, Zapier, Make or n8n on every visit, reward or new card, and verify its HMAC signature. A webhook is an address on your server that StampyWallet calls on every counter event: a visit, a reward, a new card, a gift card sold. It is what connects StampyWallet to a POS, a CRM, a spreadsheet or a no-code tool, without an official integration. > **The other way around? :** to have your POS add a stamp on every sale, use [`POST /v1/passages`](https://stampywallet.com/en/docs/api/add-visit/), or [receive sales from your POS](https://stampywallet.com/en/docs/api/incoming/) directly. ## Set up an address In **Settings › Integrations › Webhooks** (the owner and managers): 1. **Add a public `https://` address**, and tick events if you want. **No box ticked: all events**, including those added later. 2. **Copy the signing secret**, which starts with `whsec_`. It is shown only once; after that the screen only displays its last four characters. 3. **Click "Send a test"**: a sample `passage.cree`, marked `"test": true`, goes out to your address right away, and the screen shows the response received. The screen shows the last 20 deliveries (status, code, duration, attempt) and a **Resend** button on failed ones. Ten addresses at most per business; each can be disabled, re-enabled or deleted. > **Warning :** refused: an address that is not `https://`, an address that contains credentials (`https://me:secret@…`), and any private or local address (`localhost`, `10.x`, `192.168.x`…), even when resolved through DNS. All ports are accepted. Redirects are never followed: a 3xx response counts as a failure. ## What your server receives A `POST` request in JSON, with these headers: ```http title="Headers" POST /your/address HTTP/1.1 Content-Type: application/json User-Agent: StampyWallet-Webhooks/1.0 StampyWallet-Evenement: passage.cree StampyWallet-Id: evt_WdGHduXPRbExh0vh5eYtCjXo StampyWallet-Signature: t=1792648365,v1=8f3c…(64 hex characters) ``` And this body. The envelope is the same for every event; only `donnees` changes ([the event list](https://stampywallet.com/en/docs/api/events/)): ```json title="passage.cree" { "id": "evt_WdGHduXPRbExh0vh5eYtCjXo", "type": "passage.cree", "cree_le": "2026-10-22T05:52:45.694Z", "commerce": { "id": "9f1cbdfc-df71-4fab-a554-3bdbb7c71ebf", "nom": "Le Café des Crochets" }, "donnees": { "client": { "id": "40de4b86-1d7a-4cc6-add3-b7a83fa43c04", "prenom": "Inès", "telephone": "+33611223344", "email": "ines@example.com" }, "passage": { "id": "226fdea1-ba59-49fb-93e6-60a14a765134", "quantite": 1, "montant": null, "devise": "EUR", "reference": "caisse-1", "source": "scan", "piste": "normal", "tampons": 1, "objectif": 2 } } } ``` - `id`: `evt_` followed by 24 characters. **The same for every delivery of an event** (attempts, resends, and from one address to another): it is your deduplication key. - `cree_le`: when the event happened (not when it was sent), in ISO 8601 UTC. - `test`: present, set to `true`, only on the delivery from the "Send a test" button. - `donnees.client`: `{ id, prenom }`, plus `telephone` and `email` only if the customer gave them; `null` for a gift card or a package. - Amounts (`montant`, `prix`) are in **cents**, as integers, with their `devise` (ISO 4217). - New keys may appear: ignore what you don't know. ## Verify the signature Verify every request **before** trusting its content: otherwise, anyone could call your address. ```text title="The calculation" StampyWallet-Signature: t=,v1= signature = hex( HMAC-SHA256( key = secret, message = "." ) ) ``` - The key is the **whole** secret, `whsec_` prefix included. - The message is the timestamp `t`, a dot, then the body **as received**, in bytes. Don't re-serialize the JSON before verifying: rewritten JSON no longer has the same bytes. - Reject a `t` more than 5 minutes away from your clock: this prevents replaying an old request. - The header may carry several `v1=` (on the day a secret changes): one match is enough. Compare in constant time. ```js title="Node.js" const crypto = require('node:crypto'); /** corpsBrut: the received body, as a Buffer (or text), BEFORE any JSON.parse. */ function verifierStampyWallet(secret, corpsBrut, entete, tolerance = 300) { let t = null; const v1 = []; for (const morceau of String(entete || '').split(',')) { const i = morceau.indexOf('='); const cle = morceau.slice(0, i).trim(); const valeur = morceau.slice(i + 1).trim(); if (cle === 't' && /^\d+$/.test(valeur)) t = Number(valeur); if (cle === 'v1') v1.push(valeur); } if (t === null || v1.length === 0) return false; if (Math.abs(Math.floor(Date.now() / 1000) - t) > tolerance) return false; const attendue = crypto.createHmac('sha256', secret) .update(`${t}.`).update(corpsBrut).digest('hex'); return v1.some((s) => s.length === attendue.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(attendue))); } // With Express: keep the raw body on this route. app.post('/stampywallet', express.raw({ type: 'application/json' }), (req, res) => { const ok = verifierStampyWallet(process.env.STAMPYWALLET_WEBHOOK_SECRET, req.body, req.get('StampyWallet-Signature')); if (!ok) return res.status(400).end(); const evenement = JSON.parse(req.body.toString('utf8')); // deduplicate on evenement.id, then process later res.status(200).end(); }); ``` ```python title="Python" import hashlib import hmac import time def verifier_stampywallet(secret: str, corps_brut: bytes, entete: str, tolerance: float = 300) -> bool: """corps_brut: the received bytes (Flask: request.get_data()), before any json.loads.""" t, v1 = None, [] for morceau in (entete or "").split(","): cle, _, valeur = morceau.strip().partition("=") if cle == "t" and valeur.isdigit(): t = int(valeur) elif cle == "v1": v1.append(valeur) if t is None or not v1: return False if abs(int(time.time()) - t) > tolerance: return False attendue = hmac.new(secret.encode(), f"{t}.".encode() + corps_brut, hashlib.sha256).hexdigest() return any(hmac.compare_digest(s, attendue) for s in v1) # With Flask: # @app.post("/stampywallet") # def recevoir(): # if not verifier_stampywallet(os.environ["STAMPYWALLET_WEBHOOK_SECRET"], # request.get_data(), # request.headers.get("StampyWallet-Signature", "")): # abort(400) # evenement = request.get_json() # return "", 200 ``` ```php title="PHP" $tolerance) { return false; } $attendue = hash_hmac('sha256', $t . '.' . $corpsBrut, $secret); foreach ($v1 as $s) { if (hash_equals($attendue, $s)) { return true; } } return false; } // the raw body, before json_decode $corps = file_get_contents('php://input'); $ok = verifier_stampywallet(getenv('STAMPYWALLET_WEBHOOK_SECRET'), $corps, $_SERVER['HTTP_STAMPYWALLET_SIGNATURE'] ?? ''); ``` ### Test vector To check your code: with these values and an infinite tolerance (`Infinity`, `float("inf")`, `PHP_INT_MAX`), the function must return true. The body is a single line, with no trailing newline, and contains an "é" on purpose: the signature covers the UTF-8 bytes. ```text title="Vector" secret : whsec_5WZbPq8rLk2N7xYdTf3Hc9JmVg4RsQe1 t : 1761134400 corps : {"id":"evt_4fK9mQ2xT7bL1nR8vZ3cW6yH","type":"passage.cree","cree_le":"2026-10-22T12:00:00.000Z","commerce":{"id":"9b2f6c1e-4d3a-4f7b-8e21-5a6c7d8e9f01","nom":"Le Café du Coin"},"donnees":{"client":{"id":"3c1d2e4f-5a6b-4c7d-8e9f-0a1b2c3d4e5f","prenom":"Camille"},"passage":{"id":"7e8f9a0b-1c2d-4e3f-9a4b-5c6d7e8f9a0b","quantite":1,"montant":null,"devise":"EUR","reference":null,"source":"scan","piste":"normal","tampons":4,"objectif":10}}} v1 : 4346dff69a3e9e25937b22ec031a8eaf8cfce3baca4dfb932865682b8f891f1d en-tête: t=1761134400,v1=4346dff69a3e9e25937b22ec031a8eaf8cfce3baca4dfb932865682b8f891f1d ``` ## Responding, and retries - Respond with a **2xx code in less than 10 seconds**. The body of your response is ignored. Respond quickly, and do the long work afterwards. - Otherwise, **eight attempts** in total: the first right away, then after 1 min, 5 min, 30 min, 2 h, 5 h, 8 h and 8 h. The last one goes out about 23 h 30 min after the first; on the eighth failure, the delivery is abandoned. - After **50 failures in a row** on an address, it is **disabled** and the business owner receives an email. Re-enable it once your server is fixed: its counter goes back to zero. - The test delivery is only tried once and does not count toward failures. - **Resend** sends the same event again (same `id`, same bytes), with a new signature. - **At least once, with no guaranteed order**: an event may arrive twice, deduplicate on `StampyWallet-Id`; two close events may arrive out of order, rely on `cree_le`. - Deliveries are kept in the log for 30 days. | Delivery status | Meaning | |---|---| | `en_attente` | Not tried yet | | `echec` | The last attempt failed; another one is scheduled | | `livre` | A 2xx was received | | `abandonne` | Eight failures, or address disabled, or replaced by a resend | > **No code :** Zapier, Make and n8n receive these webhooks as is: paste the address of their "Webhook" trigger as the address. Step-by-step guides are on [Connect your POS](https://stampywallet.com/en/docs/api/connect-your-pos/#sans-code). Source : https://stampywallet.com/en/docs/api/webhooks/ --- # Event list The nine events sent by StampyWallet webhooks, when they are sent, and the exact content of their donnees field. Every event has the same envelope (`id`, `type`, `cree_le`, `commerce`, see [Webhooks](https://stampywallet.com/en/docs/api/webhooks/#requete)). Only `donnees` changes; it is shown on its own below. Events are sent from the counter as well as from the API: a visit scanned or added through [`POST /v1/passages`](https://stampywallet.com/en/docs/api/add-visit/) produces the same `passage.cree`. | Event | Sent when | |---|---| | `client.cree` | A customer joins the program (their card is created) | | `carte.ajoutee` | The customer saves their card in Apple Wallet or Google Wallet | | `passage.cree` | A visit is added: one or more stamps, or points | | `passage.annule` | A customer's last visit is cancelled | | `recompense.atteinte` | A visit crosses a tier: a reward is due (one per tier crossed) | | `recompense.offerte` | The reward is handed to the customer | | `recompense.annulee` | Handing over a reward is cancelled: it is due again | | `carte_cadeau.vendue` | A gift card is sold | | `forfait.vendu` | A prepaid package is sold | > **Note :** `carte.ajoutee` covers Apple Wallet and Google Wallet: `donnees.carte.wallet` is `apple` or `google`. On Apple, the event is sent for each new device (an iPhone then a watch produce two). Removing the card sends nothing. ## client.cree ```json title="donnees" { "client": { "id": "3374a06e-9608-4376-91cb-1a3fb6721b04", "prenom": "Camille", "cree_le": "2026-10-22T05:52:45.664Z" } } ``` `telephone` and `email` only appear if the customer gave them. `prenom` can be `null`. ## carte.ajoutee ```json title="donnees" { "client": { "id": "40de4b86-1d7a-4cc6-add3-b7a83fa43c04", "prenom": "Inès", "telephone": "+33611223344", "email": "ines@example.com" }, "carte": { "wallet": "apple" } } ``` ## passage.cree ```json title="donnees" { "client": { "id": "40de4b86-1d7a-4cc6-add3-b7a83fa43c04", "prenom": "Inès", "telephone": "+33611223344", "email": "ines@example.com" }, "passage": { "id": "226fdea1-ba59-49fb-93e6-60a14a765134", "quantite": 1, "montant": null, "devise": "EUR", "reference": "caisse-1", "source": "scan", "piste": "normal", "tampons": 1, "objectif": 2 } } ``` - `quantite`: what the visit is worth (stamps, or points in points mode). - `montant`: the receipt amount in cents if it was entered, otherwise `null`. - `reference`: the operation identifier sent by the caller (the API reference, or the counter's), otherwise `null`. - `source`: `scan`, `manuel`, `api`… - `piste`: `normal`, or `premium` for the second counter. - `tampons` (or `points` in points mode) and `objectif`: the counter **after** the visit. A visit that completes the card returns the next card's counter (often `0`); the reward comes in a separate `recompense.atteinte`. ## passage.annule ```json title="donnees" { "client": { "id": "40de4b86-1d7a-4cc6-add3-b7a83fa43c04", "prenom": "Inès", "telephone": "+33611223344", "email": "ines@example.com" }, "passage": { "id": "d8f62d77-88a6-44ef-a0c3-2a7d5066b90a", "quantite": 1, "source": "scan", "piste": "normal", "tampons": 0, "objectif": 2 }, "recompenses_retirees": 0, "motif": "scanned twice" } ``` `passage.id` is the one from the original `passage.cree`, and the counter is the one after the cancellation. `recompenses_retirees`: the rewards this visit had unlocked and that disappear with it. `motif`: the reason entered at the counter, or `null`. ## recompense.atteinte ```json title="donnees" { "client": { "id": "40de4b86-1d7a-4cc6-add3-b7a83fa43c04", "prenom": "Inès", "telephone": "+33611223344", "email": "ines@example.com" }, "recompense": { "id": "504c8734-3bbd-4fe9-95f8-7a67c8239dab", "libelle": "A free coffee", "palier": 2, "piste": "normal", "expire_le": null }, "passage_id": "8bfbdd5e-eff4-4a3b-94fe-1557ec6edce6" } ``` One event per tier crossed. `expire_le` is `null` if the business's rewards don't expire. ## recompense.offerte ```json title="donnees" { "client": { "id": "40de4b86-1d7a-4cc6-add3-b7a83fa43c04", "prenom": "Inès", "telephone": "+33611223344", "email": "ines@example.com" }, "recompense": { "id": "504c8734-3bbd-4fe9-95f8-7a67c8239dab", "libelle": "A free coffee", "piste": "normal", "offerte_le": "2026-10-22T05:54:51.790Z" } } ``` ## recompense.annulee ```json title="donnees" { "client": { "id": "40de4b86-1d7a-4cc6-add3-b7a83fa43c04", "prenom": "Inès", "telephone": "+33611223344", "email": "ines@example.com" }, "recompense": { "id": "504c8734-3bbd-4fe9-95f8-7a67c8239dab", "libelle": "A free coffee", "piste": "normal" }, "motif": "wrong card" } ``` ## carte_cadeau.vendue ```json title="donnees" { "client": null, "carte_cadeau": { "id": "ac7e56a5-3c00-492f-971a-21ca7d675fd6", "code": "EJ9Y-7FYN", "sorte": "montant", "produit": null, "montant": 2500, "devise": "EUR", "pour": "Lucie", "de": "Marc", "canal": "comptoir", "expire_le": "2027-04-22T05:54:51.879Z" } } ``` `sorte` is `montant` or `produit` (then `produit` carries its name and `montant` its price). Here, 2500 cents: €25. > **Warning :** `code` is the gift card code, the one in its QR code: it is enough to redeem it at the counter. Treat it as a secret: don't write it in a shared spreadsheet or in logs. ## forfait.vendu ```json title="donnees" { "client": null, "forfait": { "id": "0ec29323-ed13-4e6b-9595-963601290eac", "code": "XFF2-WTW9", "nom": "10 coffees", "unites": 10, "prix": 3000, "devise": "EUR", "titulaire": "Paul", "canal": "comptoir", "expire_le": "2027-04-22T05:54:51.891Z" } } ``` `titulaire`: the name written at the sale, or `null`. `expire_le` is `null` for a package with no expiry date. As with a gift card, keep `code` secret. > **Note :** personal data is limited to what the customer gave when signing up. Once received, it is your responsibility: only keep it as long as necessary. Source : https://stampywallet.com/en/docs/api/events/ --- # Receive events from your POS POST /v1/entrants/:jeton receives sales from Square, Shopify, SumUp, Zettle or any tool, and adds the visit to the right customer. `POST /v1/entrants/:jeton` An address to give your POS: on every paid sale, the POS calls it, and StampyWallet adds the visit to the customer's card, found by their email or phone. No key to write into the POS: the address token is enough. An unknown or revoked address responds `404`. ## Sources Each address is created for one source in **Settings › Integrations**, with an optional signing key. StampyWallet then reads that POS's format as is: | Source | What counts | Customer | Amount | Reference | |---|---|---|---|---| | `square` | `payment.created` / `payment.updated` with status `COMPLETED` | Buyer's email, or the customer's email and phone | Excluding tip | The payment id | | `shopify` | `orders/paid` | Email and phone of the customer or the order | Order subtotal | The order id | | `sumup` (beta) | `PAID` or `SUCCESSFUL` transaction | Email or phone | Transaction amount | Transaction code | | `zettle` (beta) | `PurchaseCreated` | Customer's email or phone | Purchase amount | The purchase id | | `generique` | The body of [`POST /v1/passages`](https://stampywallet.com/en/docs/api/add-visit/) | `client` | `montant` | `reference` (optional) | > **Note :** POS sources **never create a customer**: a sale with no known customer is ignored. A sale with no email or phone, or with no reference, is ignored too. Only the `generique` source can create a customer, with `creer_si_absent: true`. ## Signature When a signing key is saved for the address, every call must be signed, otherwise `401 signature_invalide`. Always paste it in when the POS provides one. | Source | Header | Signature | |---|---|---| | `generique` | `X-Stampy-Signature` | Hex HMAC-SHA256 of the raw body, with the address key (`sha256=` prefix accepted) | | `square` | `x-square-hmacsha256-signature` | Base64 HMAC-SHA256 of the notification URL followed by the raw body | | `shopify` | `X-Shopify-Hmac-Sha256` | Base64 HMAC-SHA256 of the raw body | | `sumup` | `X-Payload-Signature` | Hex HMAC-SHA256 of the raw body | | `zettle` | `X-iZettle-Signature` | Hex HMAC-SHA256 of `timestamp + "." + payload` | ## The generic format For a script, a no-code tool or a POS with no known format, send the same body as [`POST /v1/passages`](https://stampywallet.com/en/docs/api/add-visit/): ```bash title="curl" corps='{"client": {"telephone": "+33612345678"}, "montant": 18.9, "reference": "T-2026-1025-0091"}' signature=$(printf '%s' "$corps" | openssl dgst -sha256 -hmac "$STAMPY_ENTRANT_SECRET" | sed 's/^.* //') curl https://app.stampywallet.com/v1/entrants/9QkT2vX7pL4mR8sN1bC6dF3gH5jK0wYz \ -H "Content-Type: application/json" \ -H "X-Stampy-Signature: $signature" \ -d "$corps" ``` ```js title="Node.js" const crypto = require('node:crypto'); const corps = JSON.stringify({ client: { telephone: '+33612345678' }, montant: 18.9, reference: 'T-2026-1025-0091' }); const signature = crypto.createHmac('sha256', process.env.STAMPY_ENTRANT_SECRET).update(corps).digest('hex'); await fetch(process.env.STAMPY_ENTRANT_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Stampy-Signature': signature }, body: corps, }); ``` ## Responses Once the call is authenticated, the response is always `200`, so the POS learns nothing about your customer list: - `{ "ok": true, "ignore": true }`: the event is not relevant (not paid, another topic, no customer, or no reference for a POS); - `{ "ok": true }`: in every other case, whether the customer exists or not, whether the visit was added or refused (too soon, amount too small). Only a real outage responds `500`: the POS retries, and the reference ensures a new attempt does not add a stamp twice. Each address accepts 300 calls per minute. Step-by-step settings, POS by POS: [Connect your POS](https://stampywallet.com/en/docs/api/connect-your-pos/). Source : https://stampywallet.com/en/docs/api/incoming/ --- # Connect your POS Step-by-step settings so Square, Shopify, SumUp, Zettle, Zapier, Make or n8n add a stamp on every sale, with no developer. Connecting your POS means giving it a StampyWallet address to call on every paid sale. The visit is then added automatically to the customer's card, found by their phone or email. Nothing is required to get started: scanning at the counter is enough, the POS is a bonus, especially for a [points card](https://stampywallet.com/en/docs/guide/card-types/#points). > **Note :** the POS only knows the customer if it has their phone or email (customer profile, receipt sent by email, online order), and the customer must already have their StampyWallet card: a POS never creates a customer. An anonymous sale is ignored: for those, scanning at the counter is still the right move. ## In StampyWallet, first 1. **Open Settings › Integrations** and choose **Receive sales from a POS**. 2. **Choose the POS** (Square, Shopify, SumUp, Zettle, or "Other" for the generic format). 3. **Copy the address** created, in the form `https://app.stampywallet.com/v1/entrants/ent_…`. ## Then in your POS **Square** 1. Open the **Square Developer Dashboard**, create an application (or open yours), then **Webhooks › Subscriptions › Add subscription**. 2. Paste the StampyWallet address as the **Notification URL** and tick the **`payment.created`** and **`payment.updated`** events. 3. Copy the subscription's **Signature key** and paste it into Settings › Integrations. 4. Only payments that reach `COMPLETED` count. **Shopify** 1. In the Shopify admin: **Settings › Notifications › Webhooks › Create webhook**. 2. Event: **Order payment** (`orders/paid`), format **JSON**, address: the StampyWallet one. 3. Copy the secret shown under the list of webhooks ("Your webhooks will be signed with…") and paste it into Settings › Integrations. 4. The customer is found by the order's email or phone. **SumUp** 1. SumUp sends its transaction notifications to the address set by an application linked to your account. From your SumUp developer area, declare the StampyWallet address for successful transactions. 2. Paste the signing secret provided, if there is one, into Settings › Integrations. 3. Beta: write to us if a sale doesn't come through, we'll look into it with you. **Zettle** 1. Zettle (PayPal) sends its events through its webhooks API: create a subscription to the purchase created event (`PurchaseCreated`) pointing to the StampyWallet address. 2. Paste the subscription's signing key into Settings › Integrations. 3. Beta: write to us if a sale doesn't come through. ## No code: Zapier, Make, n8n Both ways: a no-code tool can **send** StampyWallet the sales of a POS it knows, or **receive** StampyWallet events to fill a spreadsheet, a CRM or an email list. **Zapier** **Send a sale to StampyWallet** 1. Trigger: your POS or store ("New Payment", "New Order"…). 2. Action: **Webhooks by Zapier › POST**, URL: the "Other" address (generic format), data type: JSON. 3. Body: `{"client": {"telephone": "…"}, "montant": …, "reference": "…"}` (or `email` instead of `telephone`), the reference being the sale number. **Receive StampyWallet events** 1. Trigger: **Webhooks by Zapier › Catch Hook**. Copy the hook address. 2. Paste it as an endpoint in Settings › Integrations ([Webhooks](https://stampywallet.com/en/docs/api/webhooks/)), then click "Send a test". **Make** **Send a sale to StampyWallet** 1. Your POS module as the trigger, then the **HTTP › Make a request** module. 2. Method `POST`, URL: the "Other" address, JSON body `{"client": {"telephone": "…"}, "montant": …, "reference": "…"}`, as in [`POST /v1/passages`](https://stampywallet.com/en/docs/api/add-visit/). **Receive StampyWallet events** 1. **Webhooks › Custom webhook** module, copy the address. 2. Paste it as an endpoint in Settings › Integrations, then "Send a test" so Make learns the structure. **n8n** **Send a sale to StampyWallet** 1. Your POS node as the trigger, then an **HTTP Request** node. 2. Method `POST`, URL: the "Other" address, JSON body `{"client": {"telephone": "…"}, "montant": …, "reference": "…"}`, as in [`POST /v1/passages`](https://stampywallet.com/en/docs/api/add-visit/). **Receive StampyWallet events** 1. **Webhook** node (POST method), copy the production address. 2. Paste it as an endpoint in Settings › Integrations. To verify the signature, add a **Code** node with the Node.js example from the [Webhooks](https://stampywallet.com/en/docs/api/webhooks/#signature) page. ## Another POS If your POS can call an address on every sale (many can, under the name "webhook" or "notification"), use the "Other" address and the [generic format](https://stampywallet.com/en/docs/api/incoming/#le-format-generique). Otherwise, write to us from the [contact](site:contact/) page: we'll look into it with you. ## Frequently asked questions **Does it work with my POS?** Settings are ready for Square, Shopify, SumUp and Zettle. Any other POS that can call an address on each sale goes through the generic format, and a POS that Zapier, Make or n8n know can go through them. Otherwise, write to us: we will look at it with you. **Do I need a developer?** No. You copy an address in StampyWallet, paste it into your POS, then paste into StampyWallet the signing secret your POS gives you, if there is one. **Does my customer need to have their card already?** Yes. A POS never creates a customer: it finds the card by the customer's phone or email. An anonymous sale is ignored, and scanning at the counter remains the right move. **Can a sale count twice?** No. Each sale carries its reference: if the POS retries after a connection drop, the visit is not added a second time. **Is the POS connection included in my subscription?** It is part of the Growth plan, at €40 excl. VAT per month, and open during the 30-day free trial. It is not included in the Essential plan. Source : https://stampywallet.com/en/docs/api/connect-your-pos/ --- # Install the "beautiful cards" skill A Markdown file that teaches your AI assistant to design a business's loyalty cards the way we do: its real images first, the fourteen card shapes, icons that say what the reward is, and a review before showing anything. https://stampywallet.com/en/docs/skill.md ## Installation **Claude** 1. **Download** [skill.md](skill.md). 2. In Claude, open **Settings › Capabilities › Skills** and add the file (Claude accepts a zipped folder containing `SKILL.md`: rename the file if the interface asks for it). 3. **Add the StampyWallet connector** (`https://app.stampywallet.com/mcp`) so the cards are really rendered: [Connect Claude](https://stampywallet.com/en/docs/mcp/claude/). 4. Ask: "Design the loyalty cards for [business name, city]." **ChatGPT** 1. **Copy** the file (Copy button above). 2. In a **ChatGPT Project**, paste it into the project instructions. 3. **Add the StampyWallet connector**: [Connect ChatGPT](https://stampywallet.com/en/docs/mcp/chatgpt/). 4. In a conversation in the project: "Design the loyalty cards for [business name]." **Cursor and agents** 1. Give your agent the raw address: `https://stampywallet.com/en/docs/skill.md`, or copy the file into your agent's skills or rules folder (for example `.claude/skills/beautiful-cards/SKILL.md` for Claude Code). 2. Add the MCP server `https://app.stampywallet.com/mcp` to its configuration: [Cursor and agents](https://stampywallet.com/en/docs/mcp/cursor/). 3. Ask: "Read the skill, then design the loyalty cards for this business." ## What the file contains ```markdown title="skill.md" --- name: beautiful-loyalty-cards description: Design at least 12 versions of a business's loyalty card, with its real images, through the StampyWallet MCP, reviewed one by one. --- # Beautiful loyalty cards ## 1. Get to know the business ## 2. Find its real images ## 3. Render at least 12 versions, across all types ## 4. Review each card, full size ## 5. Present the choice ## 6. Put it live ``` The method, step by step: [What the skill does](https://stampywallet.com/en/docs/skill/method/). ## Frequently asked questions **Do I need a StampyWallet account?** No. The skill and the `concevoir_carte` tool work without an account. The account is only needed to put the card live, and the `creer_commerce` tool creates it for you. **Does the skill work without the MCP server?** It guides the assistant, but without the MCP server the assistant cannot render real card images: it can only describe them. **Can I edit it?** Yes, it is a plain Markdown file. Keep the real images rule and the review: they are what make the difference. **Are the business's images saved?** No. `concevoir_carte` shows them on the preview without saving anything. They are only placed on the card if the business owner puts it live. Source : https://stampywallet.com/en/docs/skill/ --- # What the skill does The fourteen card shapes, real images first, licensed icons when the photos don't fit, and a review of every card before showing it. ## The fourteen shapes The skill asks for at least twelve versions, one per shape of the card engine, so the business owner sees everything that is possible before choosing: | Mode | Shapes | What for | |---|---|---| | Stamps | grid, circles, free, objects | One reward after N visits | | Multiple rewards | path, showcase, medals, fill, ring | Better and better rewards | | Points | fill, ring, path, showcase, medals | Points based on what the customer spends | Each shape is rendered with [`concevoir_carte`](https://stampywallet.com/en/docs/mcp/tools/concevoir_carte/), parameters `mode` and `forme`. The modes in detail: [Card types](https://stampywallet.com/en/docs/guide/card-types/). ## Real images first The business's logo (`logo_url`) and photos of its products (`paliers[].image_url`) come before any illustration. With no color given, the card takes the logo's colors. Reward photos are cut out, in gray before they are earned, in color after. > **Warning :** a business owner's images are used for THEIR card, the one offered to them. They are not published anywhere else, and an illustration is never presented as a photo of the business. ## Icons and licenses When a photo doesn't fit a reward (blurry, off topic, unreadable once shrunk), the skill looks for an icon or illustration that says what the reward is at a glance, with a clear license: - **Lucide**, **Phosphor**, **Tabler** icons (MIT or ISC), recolored in the business's colors; - **Unsplash** or **Pexels** photos and illustrations; - or one of the sixty or so built-in icons (`paliers[].icone`). ## Review each card Before showing anything, each card is opened full size: full name, rewards readable and not cut off, contrasted text, sharp logo, photos recognizable even in gray. A card that fails is redone. ## Present, then put it live One numbered sheet per type, one line per version, and always the same sentence: every image and every icon can be changed. The business owner picks a number; only then does [`creer_commerce`](https://stampywallet.com/en/docs/mcp/tools/creer_commerce/) put the card live (preview, then approval). ![Example sheet of cards](https://placehold.co/600x400 "A numbered sheet: one card per shape, grouped by type.") Source : https://stampywallet.com/en/docs/skill/method/