How to Build an Invoicing Agent with MoltPe (2026)
What you will build
A small Node service that exposes POST /invoices for the human or another agent to call, sends a polished invoice email with a payment link, watches for payment via MoltPe webhooks, and marks the invoice paid automatically. Overdue invoices trigger an AI-drafted reminder. A daily reconciliation job verifies that every incoming USDC transaction matches an open invoice, and flags strays for human review.
This is the workflow you would have built with Stripe Invoicing or QuickBooks, but powered by USDC — no chargebacks, no 2.9% + 30¢, and every settlement is final within seconds.
Prerequisites
- Node 20+, npm, and a database (SQLite for the tutorial, Postgres for production).
- A MoltPe account with an API key.
- An email sender (Resend, Postmark, or SendGrid — all work).
- A publicly reachable URL for webhook delivery (ngrok in dev, Cloud Run / Vercel in prod).
Step 1 — Create the receiving wallet
Create one receiving wallet per entity (company or freelancer). Do not reuse the same wallet across clients — separate wallets make memo collisions impossible and audit trails cleaner.
curl -X POST https://api.moltpe.com/v1/agents/create \
-H "Authorization: Bearer $MOLTPE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "invoicing-agent-primary", "network": "polygon" }'
{
"agent_id": "ag_01JMV1A2B3C4D5E6F7G8H9J0KL",
"wallet_address": "0xA7c9E1f3B5a7C9e1F3b5A7c9E1f3B5a7C9e1F3b5",
"network": "polygon"
}
Step 2 — Invoice data model
Keep the schema small. Every field here maps to a business question you will eventually be asked: "Is this invoice paid? Who owes what? When is it due?"
-- invoices.sql — SQLite schema for the invoicing agent.
CREATE TABLE invoices (
id TEXT PRIMARY KEY, -- ULID
client_email TEXT NOT NULL,
client_name TEXT NOT NULL,
amount_usdc TEXT NOT NULL, -- string to avoid float drift
description TEXT,
memo TEXT NOT NULL UNIQUE, -- matched against incoming tx
status TEXT NOT NULL, -- open | paid | overdue | cancelled
due_date TEXT NOT NULL, -- ISO 8601
created_at TEXT NOT NULL,
paid_at TEXT,
tx_hash TEXT
);
Step 3 — Generate and send invoices
The /v1/invoices/create endpoint returns a payment URL and a memo. Persist both, then email the client. Use an LLM to craft a friendly, professional message that includes the payment link and due date.
// invoicer.js — create a MoltPe invoice, store it, email the client.
import express from "express";
import { ulid } from "ulid";
import Database from "better-sqlite3";
import { Resend } from "resend";
import OpenAI from "openai";
const db = new Database("invoices.db");
const resend = new Resend(process.env.RESEND_API_KEY);
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const RECEIVING_AGENT = "ag_01JMV1A2B3C4D5E6F7G8H9J0KL";
const app = express();
app.use(express.json());
app.post("/invoices", async (req, res) => {
const { client_email, client_name, amount_usdc, description, due_date } = req.body;
const id = ulid();
// 1. Ask MoltPe to issue an invoice tied to the receiving wallet.
const mp = await fetch("https://api.moltpe.com/v1/invoices/create", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.MOLTPE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
agent_id: RECEIVING_AGENT,
amount: amount_usdc,
token: "USDC",
network: "polygon",
due_date,
external_id: id,
}),
}).then(r => r.json());
// 2. Persist the invoice locally with the MoltPe memo.
db.prepare(`INSERT INTO invoices
(id, client_email, client_name, amount_usdc, description, memo, status, due_date, created_at)
VALUES (?, ?, ?, ?, ?, ?, 'open', ?, datetime('now'))`).run(
id, client_email, client_name, amount_usdc, description, mp.memo, due_date
);
// 3. AI-drafted email body.
const msg = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [{
role: "user",
content: `Write a warm, professional invoice email for ${client_name}. Amount: ${amount_usdc} USDC. Due: ${due_date}. Payment link: ${mp.payment_url}. Keep under 80 words.`
}],
});
await resend.emails.send({
from: "billing@yourcompany.com",
to: client_email,
subject: `Invoice ${id} — ${amount_usdc} USDC`,
text: msg.choices[0].message.content,
});
res.json({ invoice_id: id, memo: mp.memo, payment_url: mp.payment_url });
});
Step 4 — Handle payment webhooks
Register a webhook endpoint with MoltPe so you get notified the instant a payment settles. Verify the signature, look up the invoice by memo, and mark it paid.
import crypto from "node:crypto";
app.post("/webhooks/moltpe", express.raw({ type: "application/json" }), (req, res) => {
// Verify signature to confirm MoltPe sent this.
const sig = req.headers["moltpe-signature"];
const expected = crypto
.createHmac("sha256", process.env.MOLTPE_WEBHOOK_SECRET)
.update(req.body)
.digest("hex");
if (sig !== expected) return res.status(401).send("bad signature");
const event = JSON.parse(req.body.toString());
if (event.type === "invoice.paid") {
db.prepare(`UPDATE invoices SET status='paid', paid_at=datetime('now'), tx_hash=?
WHERE memo=?`).run(event.data.tx_hash, event.data.memo);
}
res.sendStatus(200);
});
Sample invoice.paid payload:
{
"type": "invoice.paid",
"id": "evt_01JMV9R2X5B7C9D1E3F5G7H9K1",
"data": {
"invoice_id": "inv_01JMV1A2B3C4D5E6F7G8H9J0KL",
"memo": "mv-7f3a9c2e",
"amount": "1500.00",
"token": "USDC",
"tx_hash": "0x9f1d3b5a7c9e1f3b5a7c9e1f3b5a7c9e1f3b5a7c9e1f3b5a7c9e1f3b5a7c9e1f",
"paid_at": "2026-04-25T14:02:11Z"
}
}
Step 5 — Reminders and reconciliation
A cron job, run once per day, handles both overdue reminders and reconciliation. Keep it simple — one SQL query, one email template, one transactions pull.
// cron.js — runs daily. Sends reminders and verifies books match.
const overdue = db.prepare(`SELECT * FROM invoices WHERE status='open' AND due_date < date('now')`).all();
for (const inv of overdue) {
await resend.emails.send({
from: "billing@yourcompany.com",
to: inv.client_email,
subject: `Reminder: Invoice ${inv.id} is past due`,
text: `Hi ${inv.client_name}, just a reminder that ${inv.amount_usdc} USDC is past due. Pay here: https://yourcompany.com/pay/${inv.memo}`,
});
db.prepare(`UPDATE invoices SET status='overdue' WHERE id=?`).run(inv.id);
}
// Reconciliation: pull incoming transactions, flag anything with no matching memo.
const txs = await fetch(`https://api.moltpe.com/v1/transactions?agent_id=${RECEIVING_AGENT}&since=${new Date(Date.now() - 86400000).toISOString()}`, {
headers: { "Authorization": `Bearer ${process.env.MOLTPE_API_KEY}` },
}).then(r => r.json());
for (const tx of txs.items) {
const match = db.prepare(`SELECT id FROM invoices WHERE memo=?`).get(tx.memo);
if (!match) console.warn("UNMATCHED TX — manual review needed:", tx.tx_hash);
}
Testing the agent
Create a test invoice with a small amount and a short due date. Pay it from a MoltPe test wallet. Watch the webhook fire, the DB update, and the status page flip to paid.
curl -X POST http://localhost:8080/invoices \
-H "Content-Type: application/json" \
-d '{
"client_email": "test@example.com",
"client_name": "Test Client",
"amount_usdc": "5.00",
"description": "API consulting — 1 hour",
"due_date": "2026-05-02"
}'
Then, from a funded MoltPe test wallet, send 5.00 USDC to the wallet_address with the memo. Within seconds the webhook should fire and your DB row flips to status='paid'. For the overdue path, create an invoice with due_date in the past and run the cron manually — the reminder email should land and the status should update to overdue.
Production checklist
- Use Postgres, not SQLite, so multiple app instances can share state.
- Validate all inputs on
/invoices— amounts, emails, dates. Reject invalid requests with 400 before calling MoltPe. - Store the webhook secret in a secret manager. Rotate monthly.
- Build a public status page at
/pay/:memoshowing amount, due date, payment address, and paid status. This is what clients bookmark. - Archive paid invoices in cold storage after 90 days — keeps the hot DB small and audits fast.
Frequently Asked Questions
Do clients need a crypto wallet to pay?
They need any USDC-compatible wallet — Coinbase, MetaMask, a MoltPe agent wallet, or another payment app that supports USDC on Polygon or Base. The invoice page includes a QR code and copy-paste address. For large enterprise clients, most already have treasury wallets and can pay without new setup.
How does the agent know which invoice got paid?
Each invoice gets a unique memo (a short ULID). The client is instructed to include the memo in the on-chain transaction reference field. MoltPe parses the memo from the settled transaction and fires invoice.paid webhook with the matching invoice_id. No manual matching needed.
What if a client pays the wrong amount?
MoltPe fires invoice.partial_payment for underpayment and invoice.overpayment for overpayment. Your handler can auto-refund overpayments, hold partial payments in a pending state and email the client to top up, or flag for manual review. Short answer: never auto-close on partial — always ask first.
Can the invoicing agent issue tax documents?
The agent can generate receipts with line items, amounts, and transaction hashes — which is enough for most bookkeeping workflows. Formal tax documents (like GST invoices in India or VAT invoices in the EU) are jurisdiction-specific and should be reviewed by a qualified accountant in your region. This guide does not provide tax advice.
How do I handle invoice cancellations?
Call POST /v1/invoices/{id}/cancel before payment arrives. MoltPe deactivates the memo so future payments to it bounce back. If a client has already paid, cancel the invoice record in your DB and issue a full refund through POST /v1/payments/refund with the original payment_id.
Send your first USDC invoice today
The whole agent fits in one file. Start with a 5 USDC test invoice — you will see payment confirmation in seconds.
Create a receiving wallet →About MoltPe
MoltPe is AI-native payment infrastructure that gives AI agents isolated wallets with programmable spending policies for autonomous USDC transactions. Live on Polygon PoS, Base, and Tempo. Supports REST, MCP, and x402.