Stripe Horoscope Agent
Payment Protocol + Stripe Checkout. This example is a seller agent that:
- Chats using the Agent Chat Protocol
- Requests a $1 USD Stripe payment using the Agent Payment Protocol (
payment_method="stripe") - After payment, generates a "horoscope of the day" using ASI:One (
model="asi1")
This flow needs a UI that understands RequestPayment.metadata["stripe"] and can render Stripe embedded Checkout. Use the Agentverse inspector (the path in End-to-end below). Plain ASI:One chat often will not show the checkout card.
Source code (recommended)
The full runnable example lives in the examples repo:
Keeping code in the examples repo avoids overwhelming docs with large code blocks.
Related payment examples
Compare payment rails:
- Skyfire Image Agent — USDC via Skyfire
- FET Image Agent — on-chain FET
- This page — Stripe card checkout (USD)
Protocol reference: Agent Payment Protocol
Prerequisites
- Python 3.11+
- ASI:One API key: create one from the ASI:One developer page
- Stripe test API keys (required for this tutorial):
- Use a Stripe sandbox or test mode (no real money). See Stripe Sandboxes.
- Get your test keys (publishable + secret). See Stripe API keys.
- Copy:
- Secret key (
sk_test_...) →STRIPE_SECRET_KEY - Publishable key (
pk_test_...) →STRIPE_PUBLISHABLE_KEY
- Secret key (
- Agentverse account (logged in): local
mailbox=Trueagents connect through the inspector. Open the inspector URL printed at startup and choose Connect → Mailbox. Agentverse issues the mailbox token in that UI. The example.env.exampledoes not useAGENTVERSE_API_KEY. See Mailbox Agents. - Unique
AGENT_SEED: the default seedstripe-horoscope-agent-testproduces a shared agent address for every reader who leaves it unchanged. Set your own seed in.envbefore running so you do not collide with other local copies.
Use sk_test_... / pk_test_... while following this page. Do not put sk_live_ keys in .env until you have completed Stripe's go-live checklist and production hardening.
Tested versions
Pin these versions (same as other Payment Protocol examples in this lab). After pip install -r requirements.txt, install the pins if the examples repo still lists older uagents numbers:
uagents==0.25.5
uagents-core==0.4.9
python-dotenv==1.0.1
openai
stripe
Run locally
- Clone the example.
git clone https://github.com/fetchai/innovation-lab-examples.git
cd innovation-lab-examples/stripe-horoscope-agent
- Create a virtualenv and install deps.
macOS / Linux:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install "uagents==0.25.5"
Windows (cmd):
python -m venv .venv
.venv\Scripts\activate
pip install -r requirements.txt
pip install "uagents==0.25.5"
- Copy the env template, then edit
.envbefore starting.config.pyraises if keys are missing.
macOS / Linux:
cp .env.example .env
Windows (cmd):
copy .env.example .env
Fill at least:
ASI_ONE_API_KEY=your_asi_one_api_key
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
AGENT_SEED=change-this-to-a-unique-seed
AGENT_PORT=8012
- Start the agent.
python3 agent.py
Optional Stripe config
These live in .env.example and are read by config.py / stripe_payments.py. The example always charges a fixed amount (STRIPE_AMOUNT_CENTS, default 100 = $1.00). Dynamic pricing is an extension, not the default path.
| Variable | Default | Purpose |
|---|---|---|
STRIPE_AMOUNT_CENTS | 100 | Checkout unit_amount (cents) |
STRIPE_CURRENCY | usd | Checkout currency |
STRIPE_PRODUCT_NAME | Daily horoscope | Product name on the session |
STRIPE_SUCCESS_URL | https://agentverse.ai/payment-success | Base for return_url |
STRIPE_CHECKOUT_EXPIRES_SECONDS | 1800 | Session expiry; Stripe requires at least ~30 minutes (clamped to [1800, 86400]) |
Key files (what to read first)
agent.py: tiny entrypoint; loads env and includes the chat + payment protocols. (GitHub)handlers.py: the main state machine (sign → payment → horoscope). (GitHub)stripe_payments.py: creates embedded Stripe Checkout sessions and verifiespayment_status. (GitHub)llm.py: ASI:One calls + prompts (normal reply vs horoscope generation).state.py: short-livedctx.storagestate + zodiac parsing helpers.chat_proto.py/payment_proto.py: protocol wrappers that keep boilerplate out of the handlers.
How payments are enabled (Payment Protocol + Stripe)
This example combines:
- the Agent Payment Protocol (for requesting/committing/completing payments), and
- Stripe embedded Checkout (as the payment rail when
Funds.payment_method == "stripe").
Flow
Steps: RequestPayment → embedded Checkout → CommitPayment → CompletePayment (or CancelPayment) → horoscope.
The screenshot below is the Payment Protocol workflow (RequestPayment through CompletePayment / CancelPayment).

If you have not read the protocol docs yet, start here:
1) Enable the Agent Payment Protocol (seller)
To make an agent "payable", you include the payment protocol with seller role:
payment_proto.py: wrapspayment_protocol_specwithrole="seller"and routes payment messages to handlers. (GitHub)
from uagents import Context, Protocol
from uagents_core.contrib.protocols.payment import (
CancelPayment,
CommitPayment,
CompletePayment,
RejectPayment,
payment_protocol_spec,
)
def build_payment_proto(on_commit, on_reject) -> Protocol:
proto = Protocol(spec=payment_protocol_spec, role="seller")
@proto.on_message(CommitPayment)
async def _on_commit(ctx: Context, sender: str, msg: CommitPayment):
await on_commit(ctx, sender, msg)
@proto.on_message(RejectPayment)
async def _on_reject(ctx: Context, sender: str, msg: RejectPayment):
await on_reject(ctx, sender, msg)
return proto
agent.py: includes the payment protocol in the agent. (GitHub)
agent.include(build_chat_proto(on_chat), publish_manifest=True)
agent.include(build_payment_proto(on_commit, on_reject), publish_manifest=True)
In this flow you will typically handle:
RequestPayment(seller → buyer/UI): "Here is what to pay and how."CommitPayment(buyer/UI → seller): "I paid; here is the transaction id."CompletePayment(seller → buyer/UI): "Payment verified and accepted."CancelPayment(seller → buyer/UI): afterCommitPayment, the seller aborts (unpaid, wrong method, verify failed).RejectPayment(buyer → seller): the buyer declines aRequestPayment. The seller handles it; the seller does not sendRejectPaymentin reply toCommitPayment.
Valid edges from payment_protocol_spec in this repo:
RequestPayment→{CommitPayment, RejectPayment}CommitPayment→{CompletePayment, CancelPayment}
2) Add Stripe: create an embedded Checkout Session
Stripe integration in the example lives in stripe_payments.py. Match the repo: expires_at (Stripe minimum ~30 minutes), Checkout metadata, and extra return_url query params (chat_session_id, user). Env: STRIPE_SUCCESS_URL, STRIPE_CHECKOUT_EXPIRES_SECONDS.
create_embedded_checkout_session(...)- calls
stripe.checkout.Session.create(ui_mode="embedded", ...) - returns a dict containing the publishable key, client secret, and Checkout Session ID
- calls
def create_embedded_checkout_session(*, user_address: str, chat_session_id: str, description: str) -> dict:
return_url = (
f"{STRIPE_SUCCESS_URL}"
f"?session_id={{CHECKOUT_SESSION_ID}}"
f"&chat_session_id={chat_session_id}"
f"&user={user_address}"
)
session = stripe.checkout.Session.create(
ui_mode="embedded",
redirect_on_completion="if_required",
payment_method_types=["card"],
mode="payment",
return_url=return_url,
expires_at=_stripe_expires_at(),
line_items=[
{
"price_data": {
"currency": STRIPE_CURRENCY,
"product_data": {"name": STRIPE_PRODUCT_NAME, "description": description},
"unit_amount": STRIPE_AMOUNT_CENTS,
},
"quantity": 1,
}
],
metadata={
"user_address": user_address,
"session_id": chat_session_id,
"service": "daily_horoscope",
},
)
return {
"client_secret": session.client_secret,
"id": session.id,
"checkout_session_id": session.id,
"publishable_key": STRIPE_PUBLISHABLE_KEY,
"currency": STRIPE_CURRENCY,
"amount_cents": STRIPE_AMOUNT_CENTS,
"ui_mode": "embedded",
}
verify_checkout_session_paid(checkout_session_id)- calls
stripe.checkout.Session.retrieve(checkout_session_id) - checks
payment_status == "paid"
- calls
def verify_checkout_session_paid(checkout_session_id: str) -> bool:
session = stripe.checkout.Session.retrieve(checkout_session_id)
return getattr(session, "payment_status", None) == "paid"
See the implementation:
- Stripe session creation:
create_embedded_checkout_session - Stripe payment verification:
verify_checkout_session_paid
Embedded Checkout (what "embedded" means)
ui_mode="embedded"means your UI renders Stripe Checkout inside the page (instead of redirecting to a hosted checkout page).- Stripe returns a
client_secret(for the embedded checkout UI) and a Checkout Session ID (checkout_session_id). - Your UI uses the publishable key + client secret to render the checkout.
- Your agent uses the Checkout Session ID to verify payment status after
CommitPayment.
Default amount vs dynamic pricing (extend)
This example always uses STRIPE_AMOUNT_CENTS from .env / config.py (default 100) when creating the session in stripe_payments.py. Change that env var to change the price.
Dynamic pricing is an extension, not what the sample ships:
- Server-side computed amount (ad-hoc): compute
amount_centsin your agent (based on plan/tier/promo) and pass it intostripe.checkout.Session.create(... unit_amount=amount_cents ...). - Pre-created Stripe Prices: create Prices in Stripe and pass
line_items=[{"price": "price_...", "quantity": 1}](recommended when you have a small set of fixed tiers).
If you implement dynamic amounts, keep it safe by:
- computing price on the seller/agent side (never trust client/UI-supplied price)
- optionally verifying
amount_total/currencyon the retrieved Checkout Session before delivering the paid content.
3) Put Stripe session details into RequestPayment.metadata
The Payment Protocol is rail-agnostic. The "how to render and complete payment" details go into RequestPayment.metadata.
For Stripe embedded Checkout, the convention used in this example is:
Funds(payment_method="stripe")RequestPayment.metadata["stripe"] = <embedded checkout payload>
Payload shape (example):
{
"stripe": {
"ui_mode": "embedded",
"publishable_key": "pk_test_...",
"client_secret": "cs_test_...",
"checkout_session_id": "cs_test_...",
"currency": "usd",
"amount_cents": 100
}
}
STRIPE_PUBLISHABLE_KEY belongs in RequestPayment.metadata so the UI can render Checkout. Never put STRIPE_SECRET_KEY in metadata, chat, or any buyer-visible payload.
This is what lets Agentverse (or any compatible UI) render the embedded checkout for the user.
See where the agent constructs RequestPayment and attaches metadata["stripe"]:
handlers.pypayment request:RequestPayment(..., metadata={"stripe": ...})
checkout = create_embedded_checkout_session(...)
req = RequestPayment(
accepted_funds=[Funds(currency="USD", amount="1.00", payment_method="stripe")],
recipient=str(ctx.agent.address),
description="Pay $1 to receive your horoscope of the day.",
metadata={"stripe": checkout, "service": "daily_horoscope"},
)
await ctx.send(sender, req)
4) Commit → verify → complete (the critical contract)
After the user completes checkout, the buyer/UI sends:
CommitPayment(funds.payment_method="stripe", transaction_id="<checkout_session_id>")
In this example, transaction_id is the Stripe Checkout Session ID (e.g. cs_test_...).
Then the seller agent:
- Verifies with Stripe that
payment_status == "paid" - Sends
CompletePayment(transaction_id=...)orCancelPayment(transaction_id=..., reason=...)if verify fails - Delivers the paid content (the horoscope) only after
CompletePayment
The UI may send CommitPayment before Stripe marks the session paid. If verify fails, reply with CancelPayment and ask the user to finish checkout, then Approve/Pay again.
See the commit handler that verifies, completes, then responds:
handlers.pycommit flow:on_commit
Use CancelPayment on failed verify (protocol: after CommitPayment the seller may send CompletePayment or CancelPayment only):
from uagents_core.contrib.protocols.payment import (
CancelPayment,
CommitPayment,
CompletePayment,
)
async def on_commit(ctx: Context, sender: str, msg: CommitPayment):
if msg.funds.payment_method != "stripe" or not msg.transaction_id:
await ctx.send(
sender,
CancelPayment(
transaction_id=msg.transaction_id,
reason="Unsupported payment method (expected stripe).",
),
)
return
paid = verify_checkout_session_paid(msg.transaction_id)
if not paid:
await ctx.send(
sender,
CancelPayment(
transaction_id=msg.transaction_id,
reason="Stripe payment not completed yet. Please finish checkout.",
),
)
return
await ctx.send(sender, CompletePayment(transaction_id=msg.transaction_id))
# ... generate + send horoscope ...
Keep RejectPayment on the buyer side (declining RequestPayment) and in the seller's @proto.on_message(RejectPayment) handler.
End-to-end run + test (Agentverse inspector)
This is the required test path: local agent + Agentverse inspector + Stripe test card.
- Fill
.env(see Run locally) and start the agent.
abhimanyugangani@Abhimanyus-MacBook-Pro stripe-horoscope-agent % python3 agent.py
INFO: [stripe-horoscope-agent]: Starting agent with address: agent1q2t7gnf3wymv6g7mxghm8df5cvfcgncuf057gpngrhgggv82ynxvj0r7alj
INFO: [stripe-horoscope-agent]: Agent inspector available at https://agentverse.ai/inspect/?uri=http%3A//127.0.0.1%3A8012&address=agent1q2t7gnf3wymv6g7mxghm8df5cvfcgncuf057gpngrhgggv82ynxvj0r7alj
INFO: [stripe-horoscope-agent]: Starting server on http://0.0.0.0:8012 (Press CTRL+C to quit)
INFO: [stripe-horoscope-agent]: Starting mailbox client for https://agentverse.ai
INFO: [stripe-horoscope-agent]: Manifest published successfully: AgentChatProtocol
INFO: [stripe-horoscope-agent]: Manifest published successfully: AgentPaymentProtocol
INFO: [uagents.registration]: Registration on Almanac API successful
The address in your logs depends on AGENT_SEED. Do not expect the shared sample address from the default seed.
- Open the Agent inspector link printed in the terminal (while logged into Agentverse) and click Connect → Mailbox.

- In inspector chat:
- Send
give me my horoscope - Reply with a sign (e.g.
Leo)
- Send

- The inspector shows a Stripe payment card.
- Use Stripe test card
4242 4242 4242 4242(any future expiry, any CVC, any ZIP). See Stripe test cards. - Finish checkout first, then click PAY (this triggers
CommitPaymentto the agent). If PAY happens before Stripe reportspaid, the seller should sendCancelPayment; complete checkout and Approve/Pay again.
- Use Stripe test card

- The agent verifies Stripe payment, sends
CompletePayment, and replies with your horoscope.

Troubleshooting
| Symptom | What to do |
|---|---|
RuntimeError: Missing ASI_ONE_API_KEY | Edit .env and set ASI_ONE_API_KEY. Copying .env.example is not enough. |
RuntimeError: Missing STRIPE_SECRET_KEY / STRIPE_PUBLISHABLE_KEY | Set both Stripe test keys in .env. |
| Mailbox client starts but inspector cannot chat | Log into Agentverse, open the printed inspector URL, Connect → Mailbox. |
CancelPayment / "not completed yet" after PAY | Finish embedded checkout, wait for Stripe to mark the session paid, then Approve/Pay again. |
| Same agent address as the sample logs | Change AGENT_SEED in .env and restart. |
| No Stripe card in ASI:One chat | Use Agentverse inspector; this tutorial requires a UI that renders metadata["stripe"]. |