Invoices
Send invoices with line items and tax. Customer pays in crypto. You keep your accounting record.
An invoice is a billed document with line items, optional tax row, and optional billing addresses. The customer receives an email with a Pay button that opens the public viewer at /inv/{UUID}. Clicking Pay there creates a payment tagged source_type='invoice' in your webhooks and lands the customer on the standard checkout.
List page
Sidebar: Invoices. Most-recent 100 per mode.
- Search by invoice number, customer name, email, or linked payment.
- Filter with the Status dropdown: All, Draft, Sent, Viewed, Overdue, Paid, Cancelled, Refunded.
- Each row shows the invoice number (monospace), status badge, customer name and email, the due date, the created date, a status-relative timestamp (sent, opened, or paid), total amount, line-item count, and a Payment linked chip when a payment has been started for the invoice.
- Top-right CTA: New invoice.
Bulk actions
Tick row checkboxes to reveal Cancel selected (destructive). The action fires immediately. Invoices not in a cancellable status are skipped silently and the result banner reports a count like 4 of 5 cancelled.
Create a draft
Add line items: description, quantity, unit price, optional tax %. Use Add line to add more.
Pick the currency, set issue and due dates if needed, and customise the Tax label (e.g. VAT,
GST). The default label is Tax.
(Optional) Adjust Bill from - pre-filled from your Settings profile, but every field is editable per invoice.
Pick which chains and coins this invoice accepts. Defaults to every coin on every wallet you have.
Click Create draft. The invoice number auto-generates as INV-YYYYMMDD-NNNN unless you
provide one.
Drafts are private. Nothing is sent to the customer until you click Send invoice from the detail page.
Bill from / Bill to
Bill from is pre-filled once on first form mount from your Settings profile (business name, logo, address, phone, tax ID). Every field is editable per invoice and the value is snapshotted into the invoice at save time. Editing your Settings later does not retroactively change invoices you have already created or sent.
Bill to holds the customer's name, email (required), and an optional billing address with phone and tax ID. Both phone and tax ID appear on the printed and customer-facing invoice when set.
Line items and tax
Each line has a description, quantity, unit price, and an optional tax percent (enter 10 for 10%).
The Tax label field above the table lets you rename the tax row to anything up to 50 characters: VAT, GST, IVA, etc. The default is Tax. The label appears on the table column header, the totals row, the public viewer, and the printed invoice. Customer emails carry only the total and a link to the invoice, so the label does not appear there.
Totals are computed server-side in decimal at save time. The form preview is just that - a preview.
Send and resend
From the detail page:
- Send invoice - flips a draft to
Sentand emails the customer. Subject:Invoice <number> from <your business name> - <total> <currency> due. - Resend - re-sends the email when status is
Sent,Viewed, orOverdue. Subject:Reminder: Invoice <number> from <your business name> - <total> <currency> due. Fires theinvoice.reminderwebhook.
The from <your business name> segment is dropped from both subjects when no business name is set.
Both endpoints are rate-limited to keep merchant emails from looking like spam.
Detail page
Top-right action buttons (conditional):
| Button | When |
|---|---|
| Send invoice | Draft only |
| Edit | Draft only |
| Preview | Draft only (opens the customer-facing view with a disabled Pay button) |
| Resend | Status is Sent, Viewed, or Overdue |
| Copy public link | Any status except Draft |
| Print / PDF | Always (opens the print view - use your browser's Print dialog to save as PDF) |
| Cancel | Status is Sent or Viewed |
| Delete draft | Draft only, when no payment has spawned |
The page layout (sections stack full-width - there is no sidebar):
- Three summary tiles: Total, Due, Status.
- Line items table with subtotal, tax row, and total.
- Two cards side by side: Bill to (customer name, email, billing address) and Payment (the public invoice link with a copy button, shown for every status, plus the linked payment ID that opens the payment detail page - or
Not paid yet- and the invoice currency). An open-in-new-tab icon next to the link opens the public invoice. - Timeline card (Created, Sent, Viewed, Paid, Cancelled - when each happened).
- Accepted payment methods card.
- Terms and Internal notes cards, shown only when set.
Lifecycle
Draft -> Sent -> Viewed -> Paid -> Refunded
|
+-> Cancelled| Status | Meaning |
|---|---|
| Draft | Created but not sent. Editable. Deletable. |
| Sent | Emailed to the customer. Share URL is live. |
| Viewed | Customer opened the public URL at least once. |
| Paid | A spawned payment confirmed. |
| Overdue | Derived view: Sent past its due date without payment. An invoice the customer has already opened stays Viewed and does not flip to Overdue. Stored status is unchanged. |
| Cancelled | You cancelled (Sent or Viewed only). Public URL stops accepting payments. |
| Refunded | A previously paid invoice was refunded. |
Cancel and delete
| Action | Allowed when |
|---|---|
| Edit | Status is Draft |
| Cancel | Status is Sent or Viewed |
| Delete draft | Status is Draft and no payment has spawned |
Cancelling closes the share URL immediately. The invoice record stays in the dashboard. In-flight payments already spawned from the invoice keep working.
Public viewer (/inv/{UUID})
The customer's share URL. Drafts and soft-deleted invoices return 404.
- Your logo (or an initial avatar) and business name lead the card with a "sent you this invoice" subline, and a status pill sits alongside: Open (for
SentorViewed), Overdue, Paid, Cancelled, Refunded. - The amount due is the headline. The invoice number and issue date sit in a meta strip below it.
- Line items render as stacked description and line-total rows. A qty x unit price subline appears only when the quantity is above 1. Tax appears as its own labeled row in the totals.
- From and Billed to blocks render below the totals when you filled them in.
- For payable invoices, a large pay button that reads
Pay <amount> with crypto. Clicking it spawns a payment and hard-navigates the customer to the standard checkout. - For paid, cancelled, or refunded invoices, a banner replaces the Pay button with the appropriate message.
- Footer:
Powered by NoHoldPay.
The first time a customer opens the public URL, the platform fires the invoice.viewed webhook. Subsequent visits do not re-fire the event.
Print and PDF
Open Print / PDF from the detail page. It opens /invoices/{id}/print?auto=1 in a new tab, which auto-triggers your browser's print dialog. From there, pick Save as PDF or print on paper. There is no separate PDF-download endpoint.
The print view header reads INVOICE with the invoice number in monospace. The page shows a 3-column row for From / Billed to / Dates plus the full line-items table.
Webhooks
| Event | When |
|---|---|
invoice.sent | You sent a draft (Send invoice). Resends fire invoice.reminder only. |
invoice.viewed | The customer opened the public URL for the first time. |
invoice.paid | A spawned payment confirmed. Also triggers the customer's "Payment received" email. |
invoice.refunded | A previously paid invoice was refunded. |
invoice.reminder | You triggered Resend. |
There is no invoice.cancelled webhook. Cancelling closes the public URL but does not emit a webhook.
Payload shape (some fields vary by event, see below):
{
"invoice_id": "...",
"invoice_number": "INV-20260514-0001",
"status": "paid",
"amount": "120.00000000",
"currency": "USD",
"customer_email": "[email protected]",
"public_url": "https://checkout.noholdpay.com/inv/...",
"paid_at": "2026-05-14T12:00:00Z",
"payment_id": "...",
"due_date": "2026-05-28"
}paid_at, payment_id, and due_date are optional and depend on the event. invoice.paid always carries payment_id but omits paid_at and due_date. invoice.refunded carries refunded_at instead of paid_at. On invoice.sent, invoice.viewed, and invoice.reminder each of the three appears only when set. amount serializes at 8 decimal places ("120.00000000"), so do not assert on a 2-decimal string.
See Webhooks for signature verification.
Errors you may see
| Code | When |
|---|---|
INVALID_CUSTOMER_EMAIL | Email is empty or invalid. |
INVALID_LINE_ITEMS | Line items are empty, negative, or non-decimal. |
NO_VIABLE_OPTIONS | The chains and coins you allowed don't intersect any wallet you have. |
INVOICE_NUMBER_TAKEN | A custom invoice number collides with an existing one. |
INVOICE_NOT_EDITABLE | Edit attempted on a non-draft. |
INVOICE_NOT_SENDABLE | Send attempted on a non-draft. |
INVOICE_NOT_RESENDABLE | Resend attempted outside Sent / Viewed / Overdue. |
INVOICE_NOT_CANCELLABLE | Cancel attempted outside Sent / Viewed. |
INVOICE_NOT_DELETABLE | Delete attempted on a non-draft or after a payment spawned. |
INVOICE_NOT_PAYABLE | A customer tried to pay an invoice that is not accepting payment. |
INVOICE_ALREADY_PAID | A customer tried to pay but a non-terminal payment is already linked. |