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

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