# Invoise documentation (en) API: https://platform.invoise.me/api/v1 OpenAPI: https://docs.invoise.me/openapi.json # Agent auth reference The endpoints an automated agent uses to sign in with an EVM wallet and manage its session. Source: https://docs.invoise.me/agents/auth-reference/ All paths below use `https://platform.invoise.me/api/v1`. Send JSON with `Content-Type: application/json`. Sign-in requests do not need an idempotency key. Wallet sign-in is for agents only. A request with an `Origin` header, as every browser request has, returns `403 wallet_sign_in_agents_only`. Call these endpoints from a server or script. ## POST /auth/wallet/challenge Request a message to sign. No credentials required. | Field | Type | Value | | --- | --- | --- | | `address` | string, required | Your EVM wallet address. | | `chain_id` | integer, required | An available EVM network ID from the [API](https://docs.invoise.me/payments/networks/). | **200 response:** `challenge_id` (string) and `message` (string). Sign the returned message exactly as received using EIP-191 personal-message signing. Never send the private key. An expired or used challenge requires a new request. ## POST /auth/wallet/verify Exchange a signed challenge for a session. No bearer token required. | Field | Type | Value | | --- | --- | --- | | `challenge_id` | string, required | ID returned by the challenge request. | | `signature` | string, required | Hex signature of the exact original message. | ```json {"challenge_id":"","signature":"<0x-signature>"} ``` **200 response** (example values): ```json { "token": "", "expires_at": "2026-09-21T10:00:00Z", "user": { "user_id": "", "onboarding_required": true, "mfa_required": false } } ``` Send `token` in `Authorization: Bearer `. Use `expires_at` to track expiry. No cookie is set, so bearer calls need no CSRF header. Invalid or consumed proofs can return `401 unauthorized`. If the request times out after a one-time proof may have been consumed, start a fresh challenge instead of repeatedly sending the signature. ## MFA, when enabled Wallet sign-in does not bypass the account's second factor. If `user.mfa_required` is true, use the configured factor with this session before business calls. For an account with TOTP, send: ```http POST /api/v1/auth/totp/verify Authorization: Bearer Content-Type: application/json {"code":""} ``` MFA-protected sensitive changes can also return `403 mfa_required` when a fresh confirmation is needed. Recovery codes require the primary session; they are not a standalone login method. Other account factors are described in [Account sign-in](https://docs.invoise.me/integration/dashboard-sign-in/). ## POST /onboarding When `user.onboarding_required` is true, create the first merchant. An account that signed in only with a wallet must send the human owner's email next to `name`, so ask your human for it first: ```json {"name":"My merchant","email":"owner@example.com"} ``` Send it with the bearer session and an `Idempotency-Key`. The email becomes a sign-in method of this same account. Invoise sends the person a notice: they open the dashboard, sign in with that email using a one-time code and land in the account the agent created. | Error | Meaning | | --- | --- | | `400 email_required` | `email` is missing. Ask your human for it. | | `400 invalid_email` | The address is malformed. | | `400 disposable_email` | Disposable email addresses are not accepted. Use a permanent address. | | `400 identity_already_linked` | The email already belongs to another account. Ask for a different one. | See [Agent workflow](https://docs.invoise.me/agents/integration/) for the next steps. ## POST /auth/logout End the current session. Send its bearer token. A successful request returns `200`; future calls with the revoked session are rejected. For ongoing payment work, use a [shop API key](https://docs.invoise.me/integration/api-keys/). For setup after login, follow [Agent workflow](https://docs.invoise.me/agents/integration/). --- # Agent workflow From wallet sign-in to a paid invoice, the full path for an automated agent. Source: https://docs.invoise.me/agents/integration/ This is the setup path for an agent with a [wallet session](https://docs.invoise.me/agents/sign-in/). If you already have a shop ID and API key, go straight to step 4. All JSON writes below need `Content-Type: application/json`, your session bearer token and a saved `Idempotency-Key` unique to the action. ## 1. Select a merchant Read `GET /api/v1/me`. If `user.onboarding_required` is true, ask your human for their email address first, then complete setup: ```http POST /api/v1/onboarding Content-Type: application/json {"name":"My merchant","email":"owner@example.com"} ``` An account that signed in only with a wallet must send `email`; without it the answer is `400 email_required`. A malformed address returns `invalid_email`, a disposable one `disposable_email`, and an email that already belongs to another account `identity_already_linked`. The email becomes a sign-in method of this same account. Invoise sends the person a notice: they open the dashboard, sign in with that email using a one-time code and land in the account you created. This returns `{"completed":true}`, not the merchant ID. Next call `GET /api/v1/merchants` and save the intended merchant's `id`. A returning account can belong to several merchants. Choose explicitly; do not silently use the first entry. Do not use `POST /merchants` for onboarding — it returns `forbidden`. ## 2. Create a shop ```http POST /api/v1/merchants/{merchant_id}/shops Content-Type: application/json { "name": "Agent payments", "sandbox": true, "recipient": "" } ``` Save the returned `id` as `shop_id`. Start in sandbox for a test; create a separate live shop with `sandbox: false` afterwards. The optional EVM `delegate` is a settlement wallet that you control. Set it only if you need one. Never use an exchange deposit address for that role. You can change the recipient and the delegate later with `PATCH /api/v1/shops/{shop_id}`. Invoices and addresses already created keep the ones they were created with. A live shop can instead pay into the account's [Invoise wallet](https://docs.invoise.me/payments/wallet/): send `"payout_target":"wallet"` without `recipient`. Your human sets that wallet up in the dashboard; until then, creating an invoice returns `400 wallet_not_ready`. Solana and Tron need their own recipient; see [Solana and Tron](https://docs.invoise.me/payments/solana-and-tron/). ## 3. Issue a shop API key Use your session to [create a key](https://docs.invoise.me/integration/api-keys/) with `read` and `write`. Save the returned `ivk_` token in your secret store, then use it for the following payment calls. ## 4. Create an invoice 1. Read `GET /api/v1/networks`. Use an enabled network and a ready token, with its actual address and decimals. 2. Save an idempotency key with the invoice in your system before sending the request. 3. Call `POST /api/v1/shops/{shop_id}/invoices`: | Field | Value | | --- | --- | | `chain_id` | Selected network ID from the API; an integer. | | `token` | Selected token address from the API. | | `amount` | Invoice amount in the token’s smallest units; an integer string. | | `external_id` | Optional reference to a record in your system. | For a reusable top-up address, call `/deposits` without `amount`. To let the payer choose among several tokens or networks, send `assets` instead of `chain_id` and `token`; see [Deposits and invoices](https://docs.invoise.me/payments/deposits-and-invoices/#offer-several-tokens-or-networks). After a timeout, retry the same request with the same key. `external_id` alone does not prevent duplicates. ## 5. Wait for the address, then for payment Save `id` from the `202` response. Poll: ```bash curl 'https://platform.invoise.me/api/v1/shops/{shop_id}/issuances/{issuance_id}' \ -H 'Authorization: Bearer ivk_...' ``` Wait for a non-empty `address`, then share `payment_url`. Keep polling this same endpoint for `status` and `received`. Receive change notifications through [webhooks](https://docs.invoise.me/integration/webhooks/). Read [Invoice and deposit status](https://docs.invoise.me/payments/status/) before recording invoice payment: `funded`, `settled`, cancellation and reusable deposits have different meanings. Persist each business update once. ## Read documentation without a browser Start at [llms.txt](https://docs.invoise.me/llms.txt) for the page index and [OpenAPI](https://docs.invoise.me/openapi.json) for structured contracts. [Reading tools](https://docs.invoise.me/agents/reading/) lists Markdown and full-text exports. --- # Read docs without a browser Markdown pages, llms.txt and OpenAPI for agents and other tools. Source: https://docs.invoise.me/agents/reading/ All documentation is public. You do not need an account, browser automation or JavaScript to read it. ## Start with the index ```bash curl 'https://docs.invoise.me/llms.txt' ``` It lists English and Russian pages with direct Markdown links. Fetch only the pages needed for your task. ## Read one page Replace the final `/` in a documentation URL with `.md`: ```bash curl 'https://docs.invoise.me/payments/status.md' curl 'https://docs.invoise.me/ru/payments/status.md' ``` Start with `/start/quickstart.md` or `/ru/start/quickstart.md`. `/index.md` and `/ru/index.md` return the same Quickstart text. Markdown includes the page title, canonical source URL and the same content as the website. ## Read everything | Language | Full text | | --- | --- | | English | [English full text](https://docs.invoise.me/llms-full.txt) | | Russian | [Русский полный текст](https://docs.invoise.me/ru/llms-full.txt) | These files are generated from the same source pages on every build. They contain the guides without navigation markup. API contracts are available separately in OpenAPI and Scalar. ## Load the API contract [openapi.json](https://docs.invoise.me/openapi.json) describes the documented integration endpoints, their credentials, fields, responses and errors. Use the human [API reference](https://docs.invoise.me/api-reference/) for explanations. Use the live network catalogue for token addresses and decimals. Example addresses, IDs and credentials in the docs are placeholders, not production configuration. ## A short reading path 1. Already have a key: [create an invoice](https://docs.invoise.me/start/quickstart.md), then [check its status](https://docs.invoise.me/payments/status.md). 2. Need account setup: [wallet sign-in](https://docs.invoise.me/agents/sign-in.md), then [agent workflow](https://docs.invoise.me/agents/integration.md). 3. Before retries or receiving events: [idempotency](https://docs.invoise.me/integration/idempotency-and-errors.md) and [webhooks](https://docs.invoise.me/integration/webhooks.md). --- # Agent sign-in Authenticate an automated agent with an EVM wallet, without a browser. Source: https://docs.invoise.me/agents/sign-in/ Already have a shop API key? Skip sign-in and use the [payment API](https://docs.invoise.me/api-reference/). An agent only needs wallet sign-in when it must set up or administer an account. Wallet sign-in is for agents only. Send these requests from a server or script: a request with an `Origin` header, which every browser adds, is refused with `403 wallet_sign_in_agents_only`. The dashboard does not offer wallet sign-in or wallet linking. ## 1. Request a message Use an EVM wallet you control. Its private key stays with your signing tool and is never sent to Invoise. ```bash curl -X POST 'https://platform.invoise.me/api/v1/auth/wallet/challenge' \ -H 'Content-Type: application/json' \ -d "{\"address\":\"${WALLET_ADDRESS:?}\",\"chain_id\":${CHAIN_ID:?}}" ``` Set `WALLET_ADDRESS` to your EVM address and `CHAIN_ID` to an available EVM network ID from the [API](https://docs.invoise.me/payments/networks/). The response contains `challenge_id` and `message`. ## 2. Sign the returned message Use your wallet's EIP-191 personal-message signing method. Sign `message` exactly as received, including whitespace. This is a message signature, not a transaction. The challenge is short-lived and single-use. If it expires or is consumed, request a new one. ## 3. Exchange the signature for a session ```bash curl -X POST 'https://platform.invoise.me/api/v1/auth/wallet/verify' \ -H 'Content-Type: application/json' \ -d '{"challenge_id":"","signature":"<0x-signature>"}' ``` Save `token` securely and check `expires_at`, `user.mfa_required` and `user.onboarding_required` in the response. Invoise sets no cookie. Send the session in `Authorization: Bearer `; it needs no CSRF header. ## 4. Complete setup | Condition | Next step | | --- | --- | | `user.mfa_required: true` | Complete the configured second factor before business requests. Wallet sign-in does not bypass MFA. | | `user.onboarding_required: true` | Ask your human for their email, then create the first merchant through `/onboarding` with `name` and `email`. Without the email the answer is `400 email_required`; a disposable address returns `400 disposable_email`. | | Sign-in complete | Continue with [Agent workflow](https://docs.invoise.me/agents/integration/) to choose a merchant, create a shop and issue a shop API key. | Use the session for setup and the scoped API key for payment work. If a session expires, authenticate again. See [Auth reference](https://docs.invoise.me/agents/auth-reference/) for request and response fields. --- # API keys Create a shop API key, choose its scopes and restrict it by IP. Source: https://docs.invoise.me/integration/api-keys/ An API key lets your backend work with one shop. Create it in the dashboard, save it on your server and send it as a bearer token. ## Create a key In the shop, create a key with `read` and `write` permissions. The `ivk_` token is shown once; save it immediately. A shop can have up to 10 keys that are not revoked; see [Limits](https://docs.invoise.me/integration/idempotency-and-errors/#limits). An agent can create the same key using its login session: ```bash curl -X POST 'https://platform.invoise.me/api/v1/shops/{shop_id}/keys' \ -H 'Authorization: Bearer ' \ -H 'Idempotency-Key: ' \ -H 'Content-Type: application/json' \ -d '{"name":"backend","scopes":["read","write"]}' ``` Use a session to create or change keys. An API key cannot create another key. If the account has MFA, key management may require a fresh second factor. ## Call the API ```bash curl 'https://platform.invoise.me/api/v1/shops/{shop_id}/payments?limit=50' \ -H 'Authorization: Bearer ivk_...' ``` | Scope | Purpose | | --- | --- | | `read` | allows reading invoices and deposits. | | `write` | allows creating invoices and deposits and managing webhooks. | Request both permissions for a backend that creates payments and checks them. Do not assume `write` automatically includes `read`. The key only works within its shop's permissions. It cannot manage accounts or the merchant's team. Never put it in browser code, a payment URL or a shared chat. ## Restrict access by IP Optionally provide `allowed_ips` when creating or editing a key: ```json {"allowed_ips":["203.0.113.10","2001:db8::/32"]} ``` These are example addresses. Replace them with your backend's actual egress addresses. Up to 10 IPs or CIDR ranges are allowed per key; more returns `400 too_many_allowed_ips`. An empty list removes the restriction. ## Rotate or revoke Create a replacement key, update your backend, then revoke the old key with `DELETE /api/v1/shops/{shop_id}/keys/{key_id}`. `PATCH` on the same path can change `name`, `enabled`, `scopes` or `allowed_ips`. Omitted fields stay unchanged. Editing does not reveal or rotate the token. Keys work until disabled or revoked, subject to account and shop access. They are not a promise of permanent access. See the [API reference](https://docs.invoise.me/api-reference/) for request details. --- # Account sign-in Dashboard authentication and account security, separate from the payment API. Source: https://docs.invoise.me/integration/dashboard-sign-in/ People sign in at [platform.invoise.me](https://platform.invoise.me) with email or Google. After setup, give your backend a [shop API key](https://docs.invoise.me/integration/api-keys/). Agents that need an account session use [wallet sign-in](https://docs.invoise.me/agents/sign-in/). It is for agents only: the dashboard does not offer it, and a browser request to it is refused. Receiving payments does not require implementing every sign-in method below. ## Email and Google These routes back the dashboard. All paths use `/api/v1`: | Request | Purpose | | --- | --- | | `POST /auth/email/start` | requests a sign-in code with `{"email":"you@example.com"}` and returns `challenge_id`. | | `POST /auth/email/verify` | exchanges `challenge_id` and `code` for a session. | | `GET /auth/google` | starts the browser redirect. `/auth/google/callback` handles the provider callback. | Session responses contain `token`, `expires_at`, `csrf` and `user`. Check `user.mfa_required` and `user.onboarding_required` before proceeding. An address on a disposable email domain is refused with `400 disposable_email` when it has no account yet, including when you link an email to an account. Accounts that already exist keep signing in. ## MFA and account security Use the dashboard to manage second factors. These routes require a login session: | Request | Purpose | | --- | --- | | `POST /auth/totp/enroll` | starts enrollment and returns a setup URI. | | `POST /auth/totp/verify` | accepts `code`; add `"enroll":true` only when confirming enrollment. | | `POST /auth/recovery` | accepts a recovery code with an existing primary session. | | `POST /auth/passkeys/begin` | Start passkey registration or verification. | | `POST /auth/passkeys/finish/{id}` | Complete passkey registration or verification. | | `PATCH /auth/passkeys/{id}` | Rename a passkey. | | `DELETE /auth/passkeys/{id}` | Remove a passkey. | | `POST /auth/confirm` | checks whether the session meets the fresh-auth requirement. It does not satisfy MFA by itself. | | `POST /auth/logout` | revokes the session. | Wallet sessions are subject to the same configured MFA. An API key cannot enroll, remove or bypass account factors. ## Browser cookies or bearer tokens The dashboard uses a session cookie. Cookie-authenticated writes require the matching `Origin` and `X-CSRF-Token` headers. Server integrations send a bearer token and do not need cookie-based CSRF handling. Keep credentials server-side. For account and team operations, see the [API reference](https://docs.invoise.me/api-reference/). --- # Idempotency, errors and limits How to retry Invoise mutations safely, what the error codes mean and which limits apply. Source: https://docs.invoise.me/integration/idempotency-and-errors/ If a request times out, you do not know whether it succeeded. Retry the same operation with the same `Idempotency-Key` to avoid creating a second payment. ## One key per action 1. Generate a unique key before the first request and save it with your order. 2. Send it in `Idempotency-Key` when creating or changing a business resource. 3. After a timeout, retry the same method, path and body with that key. 4. For a new action, create a new key. The key must be non-empty and no longer than 128 bytes. Repeating a completed action returns its stored response. Changing the body under the same key returns `409 idempotency_conflict`. This applies to business mutations such as invoices, shops, keys and webhooks. Read-only GET calls and sign-in challenges do not use this mechanism. Follow each endpoint's requirements. `external_id` helps associate a payment with your order; it is not an idempotency key. ## Read errors by code Example: ```json {"error":{"code":"idempotency_conflict","message":"idempotency_conflict"}} ``` Use `error.code` in your program. Do not infer success from the response body without first checking the HTTP status. | Code | Next action | | --- | --- | | `unauthorized` | Check the credential, permissions and any IP restriction. Sign in again if the session expired. | | `mfa_required` | Complete the account's second factor using its session. | | `onboarding_required` | Complete `/onboarding` before shop operations. | | `idempotency_key_required` | Supply a saved non-empty key, at most 128 bytes. | | `idempotency_conflict` | Find the original request. Do not silently create a new payment. | | `json_content_type_required` / `invalid_json` | Send `Content-Type: application/json` and only documented fields. | | `unsupported_network` / `unsupported_asset` | Select a supported network and token. | | `network_not_ready` / `asset_not_ready` | Wait for availability or select another ready route. | | `amount_below_minimum` / `fee_exceeds_amount` | Check the amount, decimals and shop terms. | | `shop_archived` / `shop_not_found` | Check the shop and access. | | `invalid_expires_in` | Send `1d`, `7d`, `30d`, `180d` or `365d`, and only when creating an invoice. Deposits do not expire. | | `invalid_assets` / `issuance_in_group` | Send distinct `{chain_id, token}` pairs without `chain_id` and `token`; cancel or pause a group by its own `id`. See [several tokens or networks](https://docs.invoise.me/payments/deposits-and-invoices/#offer-several-tokens-or-networks). | | `solana_setup_required` / `tron_setup_required` | Set the shop's [Solana or Tron recipient](https://docs.invoise.me/payments/solana-and-tron/) first. | | `invoice_amount_limit` / `sandbox_amount_limit` | Lower the amount to within the [limit](#limits). | | `active_invoice_limit_reached` / `deposit_address_limit_reached` | Cancel invoices or disable deposits you no longer need, or ask Invoise to raise the limit. | | `api_key_limit_reached` / `too_many_allowed_ips` / `webhook_limit_reached` | Revoke unused keys, shorten the IP list or reuse an existing endpoint. | | `team_limit_reached` | Remove a member or revoke a pending invitation, or ask Invoise to raise the limit. | | `account_disabled` | Invoise staff blocked the account; the reason is in `error.details`. See [blocked accounts](https://docs.invoise.me/integration/team-and-access/#blocked-accounts). | | `rate_limited` | Wait for the number of seconds in `Retry-After` before retrying. | ## Limits When a request would exceed a limit, Invoise rejects it with the usual error body and the code below. | Limit | Value | Error | | --- | --- | --- | | Amount of one invoice | 1,000,000 tokens | `400 invoice_amount_limit` | | Amount of one sandbox invoice or simulated transfer | 10,000 tokens | `400 sandbox_amount_limit` | | Active invoices per merchant | 2,000 | `400 active_invoice_limit_reached` | | Active deposit addresses per merchant, per network family | EVM 10,000, Tron 10,000, Solana 10 | `400 deposit_address_limit_reached` | | API keys per shop that are not revoked | 10 | `400 api_key_limit_reached` | | Allowed IP addresses or CIDR ranges per key | 10 | `400 too_many_allowed_ips` | | Webhook endpoints per shop | 10 | `400 webhook_limit_reached` | | Team members and pending invitations per merchant | 10 | `400 team_limit_reached` | | Requests per minute | 1,200, of which at most 120 are not GET | `429 rate_limited` | Amount limits are in whole tokens, not base units. Every supported token is a dollar stablecoin, so 1,000,000 tokens is about $1,000,000. An invoice is active until it is paid, cancelled or expired. An invoice offered on several networks counts once. A deposit address is active until it is disabled; each network option of a deposit is a separate address. Sandbox and production are counted separately for both limits. Invoise staff can change the invoice, deposit address and team limits for a merchant. The team limit applies both when inviting and when accepting an invitation. A webhook endpoint's URL can be changed, so you can reuse an endpoint instead of adding one. Requests are counted per API key, per dashboard session, or per client IP when unauthenticated. A `429` response carries `Retry-After: 60`. ## Retry without duplicates Retry timeouts and temporary server errors with the **same key**, using increasing delays. On `429`, wait for the seconds in `Retry-After`; do not send more requests immediately. Fix invalid input and access errors before retrying. For webhooks, Invoise retries delivery. Accept the event durably, return 2xx, then process it. Deduplicate by `Invoise-Event-ID`. Older `/projects` routes are aliases of `/shops`, including their idempotency scope. Use `/shops` for new integrations. Legacy error names may use `project_*`. --- # Team and access Invite teammates, choose permissions and export payment history. Source: https://docs.invoise.me/integration/team-and-access/ Invite someone to the whole merchant account or to a single shop. Use a login session with merchant-owner access; a shop API key cannot manage the team. ## Invite a teammate ```bash curl -X POST 'https://platform.invoise.me/api/v1/merchants/{merchant_id}/invites' \ -H 'Authorization: Bearer ' \ -H 'Idempotency-Key: ' \ -H 'Content-Type: application/json' \ -d '{"provider":"email","subject":"teammate@example.com","role":"developer","shop_id":""}' ``` Use `viewer` for reading, `developer` for integration work, or `owner` for administration. Set `shop_id` to limit the invitation to one shop. Omitting it or setting it to `null` grants access across the merchant. Check the role and scope before sending. The selected shop must be active and belong to that merchant; an invalid shop does not fall back to merchant-wide access. The recipient signs in and accepts with `POST /api/v1/invites/{id}/accept`. Send an idempotency key on this request too. By default a merchant can have up to 10 members and pending invitations together. Beyond that, both inviting and accepting return `400 team_limit_reached`; Invoise staff can raise the limit. See [Limits](https://docs.invoise.me/integration/idempotency-and-errors/#limits). ## View or remove access | Request | Purpose | | --- | --- | | `GET /api/v1/merchants/{merchant_id}/team` | list grants and invitations. | | `DELETE /api/v1/merchants/{merchant_id}/invites/{id}` | revoke a pending invitation. | | `DELETE /api/v1/merchants/{merchant_id}/grants/{id}` | remove existing access. | Changes require the appropriate owner session and idempotency key. If MFA is enabled, a sensitive operation may ask for a fresh code. ## Blocked accounts If Invoise staff block a merchant, its unpaid invoices are cancelled with reason `merchant_blocked`, its deposit addresses are disabled and its API keys stop working. Members see the reason in the dashboard. A blocked user's requests, other than wallet and card requests, return `403 account_disabled` with the reason in `error.details`. ## Export payment history Download `GET /api/v1/shops/{shop_id}/export.csv` with shop read access. Use it for accounting exports, not as a payment notification. --- # Webhooks Receive payment updates, check signatures and handle retries. Source: https://docs.invoise.me/integration/webhooks/ A webhook is an HTTP request Invoise sends to your server when a payment changes. You can start with [GET status checks](https://docs.invoise.me/payments/status/) and add webhooks when you need automatic updates. ## 1. Register your endpoint ```bash curl -X POST 'https://platform.invoise.me/api/v1/shops/{shop_id}/webhooks' \ -H 'Authorization: Bearer ivk_...' \ -H 'Idempotency-Key: ' \ -H 'Content-Type: application/json' \ -d '{"url":"https://example.com/invoise","filters":["transfer","invoice","payout"]}' ``` Use your own public HTTPS URL. Local and private addresses are rejected. Save the `secret` returned when the endpoint is created: you need it to verify requests. A shop can have up to 10 endpoints. An endpoint's URL can be changed, so reuse one instead of adding another. See [Limits](https://docs.invoise.me/integration/idempotency-and-errors/#limits). ## 2. Choose the events you need | Event | Meaning | | --- | --- | | `transfer.observed` | A transfer was seen; it is not confirmed yet. | | `transfer.confirmed` | An incoming transfer has enough confirmations. It may be only a partial invoice payment. | | `transfer.reverted` | A previous transfer event was reversed. | | `invoice.closed` | The invoice closed on chain. | | `invoice.cancelled` | The checkout was cancelled or the invoice expired; this is not a refund. | | `payout.sent` | The payout was broadcast or its outgoing transfer was seen. | | `payout.confirmed` | Complete settlement accounting is available. | | `payout.reverted` | A previous payout confirmation was reversed. | | `payout.failed` | A terminal payout failure needs intervention. | For `invoice.cancelled`, `data.reason` says why: `expired` when the [invoice lifetime](https://docs.invoise.me/payments/deposits-and-invoices/#invoice-lifetime) ran out, `merchant_blocked` when Invoise staff blocked the merchant. A cancellation by the merchant has no `reason`. Subscribe by group: `transfer`, `invoice`, `payout`. Incoming transfers and outgoing payouts are different events. A temporary RPC or gas delay is not `payout.failed`. `gas.wait` and `sweep.failed` are accepted as filters, but Invoise does not send them to your endpoint. They are internal signals: `gas.wait` means a payout is waiting for network fee funds and continues on its own; `sweep.failed` means an attempt to move the funds to your recipient failed. You need no action for either. A payout that cannot complete reaches you as `payout.failed`. In a sandbox shop you can simulate both through `POST /shops/{shop_id}/sandbox/simulate` to check that a delayed payout does not break your flow. They change the payout's state only and send no webhook. ## 3. Verify before processing `Invoise-Signature` has the form `t=,v1=`. The signature is HMAC-SHA256 of `timestamp + "." + raw_body`, using your webhook secret. The following Node.js example also rejects timestamps more than five minutes from the server clock. Keep that clock synchronised and choose your tolerance deliberately. ```js import { createHmac, timingSafeEqual } from 'node:crypto'; export function verify(rawBody, header, secret) { const match = /^t=(\d+),v1=([a-f0-9]{64})$/.exec(header ?? ''); if (!match) return false; const [, timestamp, signature] = match; const age = Math.abs(Date.now() / 1000 - Number(timestamp)); if (!Number.isFinite(age) || age > 300) return false; const expected = createHmac('sha256', secret) .update(timestamp + '.') .update(rawBody) .digest(); return timingSafeEqual(Buffer.from(signature, 'hex'), expected); } ``` Pass the original request bytes as `rawBody`. Do not parse JSON and stringify it before checking the signature. Reject invalid signatures before storing or acting on their contents. ## 4. Process once A shortened payload looks like this: ```json { "id": "", "type": "transfer.confirmed", "created_at": "2026-09-19T10:00:00Z", "data": { "issuance_id": "", "shop_id": "", "external_id": "order-123", "type": "invoice", "invoice_cancelled": false } } ``` | Case | Handling | | --- | --- | | Duplicates | Deduplicate using `Invoise-Event-ID`, which matches the envelope's `id`. Save the ID and the business update atomically. | | Accepting an event | Save accepted events durably, return 2xx promptly, and process queued work afterwards. | | Retries and replays | Retries and manual replays repeat the event ID. `Invoise-Attempt-ID` changes and is not a deduplication key. | | Event order | Events can arrive out of order. Use their contents and reconcile against [current status](https://docs.invoise.me/payments/status/) when needed. | For reversals, `chain_event.reference_event_id` points to the earlier chain event. Payout payloads include a stable `payout_id`, transaction hash, amounts and fee details; unknown amounts are `null`. Their incoming-transfer ID list is capped at 100 and flags truncation. Older stored events may use `project_id`; read `shop_id` first and fall back to it if needed. Replays keep the original bytes. ## Inspect failed delivery List `GET /api/v1/shops/{shop_id}/deliveries`. Replay a delivery with `POST /api/v1/shops/{shop_id}/deliveries/{id}/replay` and a saved idempotency key. Replaying does not create a new business event. --- # Hosted checkout Share the payment page, set its name and logo, and verify payment. Source: https://docs.invoise.me/payments/checkout/ Every invoice and deposit has a ready-made payment page. Send the customer the `payment_url` returned by the API once the address is available. The page shows the token, network, address, QR code and payment status. You do not need to build a checkout yourself. ## Send the payment link Use `payment_url` exactly as returned. Do not build a URL from the issuance ID or assume a fixed format for `public_id`. The customer must send the displayed token on the displayed network. Sending a different token or using a different network does not pay the invoice. ## Set the shop's name and logo Edit branding in the dashboard or use an owner session: ```http PATCH /api/v1/shops/{shop_id}/branding Authorization: Bearer Idempotency-Key: Content-Type: application/json {"name":"My shop"} ``` Branding controls the displayed shop name and logo. Set a payment's `return_url` when creating the invoice or deposit. Neither changes the payout recipient. ## Confirm payment on your server The return link is for navigation. A customer opening it is **not proof of payment**. Use [Invoice and deposit status](https://docs.invoise.me/payments/status/) to read the status from your backend, or verify a signed [webhook](https://docs.invoise.me/integration/webhooks/). Keep API keys on the server; never put them in the payment link or browser code. --- # Deposits and invoices Create a one-time invoice or a reusable deposit address. Source: https://docs.invoise.me/payments/deposits-and-invoices/ Use an **invoice** for one order with a fixed price. Use a **deposit** for a customer balance that can be topped up repeatedly. ## Create an invoice ```bash curl -X POST 'https://platform.invoise.me/api/v1/shops/{shop_id}/invoices' \ -H 'Authorization: Bearer ivk_...' \ -H 'Idempotency-Key: ' \ -H 'Content-Type: application/json' \ -d "{\"chain_id\":${CHAIN_ID:?},\"token\":\"${TOKEN_ADDRESS:?}\",\"amount\":\"${AMOUNT:?}\",\"external_id\":\"order-123\"}" ``` Replace `{shop_id}` with your shop ID. Set `CHAIN_ID` and `TOKEN_ADDRESS` from the [API response](https://docs.invoise.me/payments/networks/) and `AMOUNT` in the selected token’s smallest units. `external_id` links the invoice to an order in your system; it does not replace the idempotency key. The response is `202 Accepted`. Save its `id` and poll the [issuance endpoint](https://docs.invoise.me/payments/status/) until `address` is available. Then give the payer the returned `payment_url`. Partial payments add up. Once enough confirmed money arrives, Invoise schedules the payout and closes the invoice. The recipient gets the invoice amount minus the service fee. **Any overpayment goes to Invoise**, not to the recipient. Invoices are one-time payments; they do not renew automatically. ## Invoice lifetime An invoice stays open for 30 days by default. To choose another lifetime, add `expires_in` to the request body, for example `"expires_in":"7d"`. Allowed values are `1d`, `7d`, `30d`, `180d` and `365d`. Invoice responses, including the public checkout, carry `expires_at`. When that time passes, an unpaid invoice closes: `cancelled_at` is set and the `invoice.cancelled` webhook is sent with `data.reason` set to `expired`. A payment that arrives later is still recorded and can still settle, as with a cancelled invoice. Deposits do not expire. Sending `expires_in` when creating a deposit returns `400 invalid_expires_in`. ## Create a deposit address Use the same request headers with this endpoint and body: ```bash curl -X POST 'https://platform.invoise.me/api/v1/shops/{shop_id}/deposits' \ -H 'Authorization: Bearer ivk_...' \ -H 'Idempotency-Key: ' \ -H 'Content-Type: application/json' \ -d "{\"chain_id\":${CHAIN_ID:?},\"token\":\"${TOKEN_ADDRESS:?}\",\"external_id\":\"customer-123\"}" ``` Do not set a fixed `amount`. Wait for the address in the same way as an invoice. The address can receive repeated top-ups in the chosen token and network. Small deposits accumulate until they reach the shop's payout threshold. Read it from `GET /api/v1/shops/{shop_id}/terms`. ## Offer several tokens or networks Send `assets` instead of `chain_id` and `token` to let the payer choose how to pay. It works for invoices and deposits: ```bash curl -X POST 'https://platform.invoise.me/api/v1/shops/{shop_id}/invoices' \ -H 'Authorization: Bearer ivk_...' \ -H 'Idempotency-Key: ' \ -H 'Content-Type: application/json' \ -d '{"assets":[{"chain_id":,"token":""},{"chain_id":,"token":""}],"amount":"10.5","external_id":"order-124"}' ``` With `assets`, the invoice `amount` is a **decimal token amount**, such as `"10.5"`, not base units: the tokens can have different decimals. Each option must reach its network's `minimum_payment`. The response is a group. Each option is a separate invoice or deposit address with its own `address`, amount in base units and status: ```json { "id": "", "group": true, "shop_id": "", "public_id": "", "status": "registering", "payment_url": "https://pay.invoise.me/", "options": [ { "id": "", "public_id": "", "chain_id": 12345, "token": "", "token_decimals": 6, "family": "evm", "amount": "10500000", "address": null, "status": "registering" } ], "expires_at": "2026-10-19T09:00:00Z" } ``` Save the group `id`. `GET /api/v1/shops/{shop_id}/issuances/{id}` returns the group with every option; poll it until the options have addresses, then give the payer the group's `payment_url`, where they pick an option. An invoice group is paid once. Its `status` follows the option that was paid, and `paid_issuance_id` names it. Webhook events are sent per option and carry `group_id` and `group_public_id`. `duplicate: true` means the group was already paid through another option: do not credit the order twice. A deposit group keeps one reusable address per option. Cancel an invoice group or pause a deposit group by the group `id`; every option changes together. An option's own `id` returns `400 issuance_in_group`. If one option cannot be created, nothing is created and `error.details` names the option as `{chain_id, token}`. An empty or repeated pair, or `assets` sent together with `chain_id` or `token`, returns `400 invalid_assets`. ## Limits One invoice can be at most 1,000,000 tokens. A merchant can have up to 2,000 active invoices and a limited number of active deposit addresses per network family. Cancel invoices or disable deposits you no longer need to free a place. See [Limits](https://docs.invoise.me/integration/idempotency-and-errors/#limits) for the exact values and error codes. ## Pause a deposit or cancel an invoice | Action | Request | | --- | --- | | Pause a deposit's checkout | `PATCH /api/v1/shops/{shop_id}/deposits/{id}` with `{"enabled":false}`. Use `true` to enable it again. | | Cancel an invoice's checkout | `POST /api/v1/shops/{shop_id}/invoices/{id}/cancel`. | Send authentication and a saved `Idempotency-Key` for either operation. These actions close the payment page. **They do not refund money or stop monitoring the address.** Funds sent to a cancelled invoice can still settle; its events carry `invoice_cancelled: true`. Funds sent after an invoice has already settled are recorded as late transfers. They are not automatically paid out. Recovery, including wrong-token recovery, requires a separate manual operation. See the [API reference](https://docs.invoise.me/api-reference/) for full request and response fields. --- # Networks and tokens Choose a network and token, format amounts and read your shop fees. Source: https://docs.invoise.me/payments/networks/ Choose a network and token from the API before creating an invoice or deposit. The token's name alone is not enough: the request needs its address on that network. ## Read available networks ```bash curl 'https://platform.invoise.me/api/v1/networks' ``` Use these fields from each network: | Field | Purpose | | --- | --- | | `chain_id` | network ID. | | `family` | `solana` or `tron` for those networks; absent or `evm` for EVM networks. | | `tokens[].address` | token address: a contract on EVM and Tron, a mint on Solana. | | `tokens[].decimals` | number of decimal places. | | `tokens[].status` | readiness, when present. Use `ready` assets for live payments. | | `minimum_payment` | the smallest invoice amount on this network, in whole tokens. | The public catalogue describes configured networks. A merchant may have a smaller enabled set; check `GET /api/v1/merchants/{merchant_id}/networks` with a session during setup. The API rejects invoice or deposit creation on an unavailable network or asset. Solana and Tron need a recipient set on the shop before the first invoice. See [Solana and Tron](https://docs.invoise.me/payments/solana-and-tron/). Networks and tokens change through platform settings. Read availability, addresses and decimals from the API when selecting an asset; do not maintain a fixed list. ## Minimum payment `minimum_payment` is a whole number of tokens, for example `"1"`. An invoice below it returns `400 amount_below_minimum`. Both network lists return it. The value differs between networks and can change, so read it instead of hardcoding it. ## Format the amount Payment amounts are **strings of whole numbers in the token's smallest units**: | Token decimals | 10 tokens in the request | | --- | --- | | 6 | `"10000000"` | | 18 | `"10000000000000000000"` | Do not send `"10.5"`. At 6 decimals, 10.5 tokens is `"10500000"`. Use integer arithmetic (`BigInt` in JavaScript) for API amounts; floating-point numbers can lose precision. One exception: an invoice that offers several tokens or networks takes a decimal token amount, such as `"10.5"`. See [several tokens or networks](https://docs.invoise.me/payments/deposits-and-invoices/#offer-several-tokens-or-networks). ## Read fees and payout thresholds ```bash curl 'https://platform.invoise.me/api/v1/shops/{shop_id}/terms' \ -H 'Authorization: Bearer ivk_...' ``` The response lists terms per network: | Field | Purpose | | --- | --- | | `invoice` and `deposit` | fee settings: `bps`, `minimum`, `maximum`. | | `sweep_threshold` | how much a deposit needs before a payout is scheduled. | | `minimum_sweep` and `maximum_sweep` | the advertised range for that setting. | `bps` means hundredths of a percent: 50 bps = 0.5%. Fee `minimum` and `maximum` use eighteen-decimal units, independently of token decimals; `10000000000000000` means a 0.01-token fee. `sweep_threshold` is a human-readable token amount, such as `"1"`. **The maximum fee is not the maximum payment amount.** Keep fees, invoice amounts and deposit thresholds separate. --- # Sandbox Test payment events without sending real crypto. Source: https://docs.invoise.me/payments/sandbox/ A sandbox shop lets you test payment handling without sending real crypto. Each merchant can have one sandbox shop. Create a separate shop with `"sandbox": true`. An existing live shop cannot be switched into sandbox mode. ## Test an invoice 1. Create an invoice in the sandbox shop using the [quickstart](https://docs.invoise.me/start/quickstart/). 2. Save its `id` as your `issuance_id`. 3. Simulate a confirmed incoming payment: ```bash curl -X POST 'https://platform.invoise.me/api/v1/shops/{shop_id}/sandbox/simulate' \ -H 'Authorization: Bearer ivk_...' \ -H 'Idempotency-Key: ' \ -H 'Content-Type: application/json' \ -d '{"issuance_id":"","kind":"transfer.confirmed","amount":"10000000"}' ``` Use the invoice's actual amount in base units. An empty `{}` body does not identify a payment and will fail. In sandbox, one invoice or simulated transfer can be at most 10,000 tokens; a larger amount returns `400 sandbox_amount_limit`. Sandbox invoices and deposit addresses are counted separately from production. See [Limits](https://docs.invoise.me/integration/idempotency-and-errors/#limits). Read the [status endpoint](https://docs.invoise.me/payments/status/) again. A fully funded invoice reaches `funded`; webhook subscribers can receive `transfer.confirmed`. The simulation inserts the requested event, not an entire real blockchain payment. To simulate invoice closure after funding, send a **new** idempotency key with: ```json {"issuance_id":"","kind":"invoice.closed","amount":"0"} ``` That tests the `settled` state. To test corrections, simulate `transfer.reverted` with `reference_event_id` set to the earlier event ID. Use a different saved key for each new simulated event. ## Start a clean test `POST /api/v1/shops/{shop_id}/sandbox/reset` deletes sandbox payment history. It requires an owner session, fresh MFA if enabled, and an `Idempotency-Key`. Old idempotency keys keep their original results after reset. Use new keys for new test payments. ## Before accepting real payments Sandbox checks your API and event handling. It does not prove real network confirmations, gas costs or receipt at an exchange. Test a small real payment separately before relying on that route. --- # Solana and Tron Set a Solana or Tron recipient and see how those payments differ from EVM ones. Source: https://docs.invoise.me/payments/solana-and-tron/ Solana and Tron networks are listed in `GET /api/v1/networks` next to EVM networks, with `family` set to `solana` or `tron`. Read their tokens, decimals and `minimum_payment` from the API; do not keep your own list. Addresses on these networks use their own base58 format in requests, responses and webhooks. ## How they differ from EVM | | EVM | Solana | Tron | | --- | --- | --- | --- | | Payment address | A contract address. The recipient is fixed on chain when the address is created. | A key per payment, held by the Invoise signer. | A key per payment, held by the Invoise signer. | | Custody | Funds can go only to the recipient fixed on chain, minus the fee. | Invoise holds a payment only until it forwards it to your recipient. | Invoise holds a payment until it forwards it. There is no payment contract. | | Recipient | The shop's `recipient` | `PUT /shops/{shop_id}/solana-recipient` | `PUT /shops/{shop_id}/tron-recipient` | | Delegate | Optional | None | None | | Sandbox shop | Yes | No: `400 solana_sandbox_unavailable` | Yes | Invoise switches Tron on for a merchant on request; ask support. Its `minimum_payment` is higher than on other networks. ## Set the recipient A shop needs a Solana or Tron recipient before it can issue on that network. Without one, creating an invoice or deposit returns `400 solana_setup_required` or `400 tron_setup_required`. ```bash curl -X PUT 'https://platform.invoise.me/api/v1/shops/{shop_id}/solana-recipient' \ -H 'Authorization: Bearer ' \ -H 'Idempotency-Key: ' \ -H 'Content-Type: application/json' \ -d '{"address":""}' ``` Use `/tron-recipient` for Tron. Setting it needs an owner session; a shop API key can only read it. `GET` on the same path returns `{"address":"..."}`, with an empty address until one is set. The recipient is set once. Sending the same address again succeeds; another address returns `400 shop_addresses_immutable`. Check the address before you save it. | Network | Rules | | --- | --- | | Solana | Use an ordinary wallet, not a program-derived address and not an Invoise address: `invalid_solana_address`. The wallet must already hold an account for the token; otherwise creating an invoice or deposit returns `solana_recipient_token_account_required`. Receive that token there once first. | | Tron | A malformed address returns `invalid_tron_address` or `invalid_tron_checksum`; an Invoise address returns `invalid_tron_recipient`. The merchant needs Tron switched on, otherwise `merchant_network_disabled`. | ## Paying into the Invoise wallet The [Invoise wallet](https://docs.invoise.me/payments/wallet/) lives on EVM networks. A shop created with payout target `wallet` also sends its Solana payments there: Invoise bridges them into the wallet on an EVM network. When bridging is not possible, issuing on Solana returns `400 solana_bridge_unavailable`. `GET /merchants/{merchant_id}/networks` shows `wallet_bridge: true` on a Solana network that can bridge. Tron payments do not reach the wallet. Choosing the wallet for Tron is refused while no Tron bridge is ready, and issuing on Tron for a shop whose Tron payouts go to the wallet returns `400 tron_bridge_unavailable`. Keep Tron on its own address. Choose per network family with `PATCH /shops/{shop_id}`: `solana_payout_target` and `tron_payout_target` take `address` or `wallet`. See the [API reference](https://docs.invoise.me/api-reference/). --- # Invoice and deposit status Check an invoice or deposit through the API, with or without webhooks. Source: https://docs.invoise.me/payments/status/ Read the invoice or deposit state through GET requests. Webhooks notify you of changes automatically. ## Read an invoice or deposit Use the `id` returned when you created the invoice or deposit: ```bash curl 'https://platform.invoise.me/api/v1/shops/{shop_id}/issuances/{issuance_id}' \ -H 'Authorization: Bearer ivk_...' ``` Relevant fields from an example response (other fields omitted): ```json { "id": "f06b8a31-19c7-4687-ade3-1c09466d2751", "kind": "invoice", "status": "funded", "amount": "10000000", "received": "10000000", "token_decimals": 6, "expires_at": "2026-10-19T10:00:00Z", "cancelled_at": null, "disabled_at": null, "payout_tx_hash": null } ``` Both amounts are integer strings in base units. This example means 10 tokens arrived, but no confirmed payout transaction is shown yet. ## Interpret an invoice | `status` | What to do | | --- | --- | | `registering` | Wait for a non-empty `address`. Do not derive one yourself. | | `open` | Keep waiting. `received` may show a partial payment. | | `funded` | Enough confirmed funds are available for settlement. | | `settled` | The invoice has closed on chain. | | `reconciling` | Wait for reconciliation; do not treat it as a successful payment. | Also check `cancelled_at`. It is a timestamp or `null`, not a status value. It is set when you cancel the invoice, when an unpaid invoice passes its `expires_at`, or when Invoise staff block the merchant; the `invoice.cancelled` webhook tells these apart by [`data.reason`](https://docs.invoise.me/integration/webhooks/#2-choose-the-events-you-need). Cancellation hides the checkout but does not stop incoming funds or settlement. Decide explicitly how your business handles a paid, cancelled invoice. `funded` means payment received, not payout completed. `settled` records invoice closure. For complete payout accounting, use `payout.confirmed`; `payout_tx_hash` identifies the latest confirmed outgoing transfer to the recipient when available. If you receive `transfer.reverted`, Invoise is reporting the reversal of a specific incoming transfer on the network. Identify it through `chain_event.reference_event_id`, read the invoice's current state and adjust your record of that transfer once. See [Webhooks](https://docs.invoise.me/integration/webhooks/) for the event format. ## Track deposits A deposit address is reusable. It can return to `open` after a payout, so do not wait for it to become `settled` permanently. `received` is **cumulative confirmed incoming money**, not the remaining balance. A payout does not subtract from it. It can decrease when a transfer is reverted. Credit each incoming payment only once; webhooks are useful for this because each event has a stable ID. `disabled_at` controls checkout availability. Disabling a deposit does not stop monitoring its address. ## List invoices and deposits ```bash curl 'https://platform.invoise.me/api/v1/shops/{shop_id}/payments?limit=50' \ -H 'Authorization: Bearer ivk_...' ``` With `limit` (1–100), the response is `{"items":[...],"next_cursor":"..."}`. Send `cursor` for the next page until `next_cursor` is empty. Without pagination parameters, the endpoint returns a limited array; it is not the whole history. The list supports `kind`, `status` and `q` filters. Its `status=paid` filter selects `funded` and `settled`; `paid` is not an issuance status returned by the detail endpoint. The detail endpoint's `transfers` field contains only the ten latest confirmed incoming/payout entries. Do not use it as a complete ledger. ## Public checkout status `GET /api/v1/checkout/{public_id}` needs no credentials. It provides the payment-page state, including `status`, `received`, `amount` and `expires_at`. Use the authenticated issuance endpoint for your backend's shop records. For pushed updates and signature checking, see [Webhooks](https://docs.invoise.me/integration/webhooks/). --- # Invoise wallet Where a shop's payments land, who controls the wallet, and what Invoise can and cannot do with it. Source: https://docs.invoise.me/payments/wallet/ A shop pays out either to an address you own or to the **Invoise wallet** of an account member. The wallet is a [Safe](https://safe.global) smart account owned by three keys with a 2-of-3 threshold: | Owner | Held by | Role | | --- | --- | --- | | Merchant key | You, in the browser only | Signs every withdrawal and setting change. Never leaves your device unencrypted. | | Invoise co-signer | Invoise signer service | Adds the second signature to what you signed. Cannot move funds alone. | | Delegate (optional) | Your own cold wallet | Together with you: exit without Invoise. Alone: queue a withdrawal through a delay module. | One wallet per account, the same address on every supported EVM network, deployed on a network the first time a withdrawal runs there. Funds stay in the network and token they arrived in; nothing is converted on receipt. ## Setting up 1. Create a shop with the payout target **Invoise wallet**. The setup opens at once and cannot be skipped while a shop waits for the wallet. 2. Choose how to unlock the key: a six-character code, a password (10+ characters) or a passkey. Codes and passwords are hardened by the signer with an oblivious PRF and locked after ten wrong attempts; a passkey uses its PRF extension where the platform supports it. 3. Without a delegate you also save a **recovery key**: the only way to recover funds if you lose the code or password. Add a delegate later on the wallet tab. ## Withdrawing Withdraw any supported token to any supported network in one confirmation. Invoise gathers USDC from other networks through Circle's CCTP, swaps on the destination when the payout token differs, then transfers to the payee. The fee is twice the gas of every step at the current price, charged in the token of each network; nothing is charged for steps that did not run. A failed final step leaves the funds on the wallet in the destination network. Protections you can turn on: a daily limit in USD, a cooldown that pauses withdrawals after a security change and makes new payee addresses wait, and a second factor on every operation. Loosening a protection takes effect only after the current cooldown. ## If Invoise is unavailable - **Delegate alone:** queues a transfer through the Zodiac Delay module; it executes after a three-day cooldown and expires after a further week. You receive an email and a cabinet banner and can cancel it before it executes. - **You and the delegate:** sign a Safe transaction directly with the recovery key and the delegate wallet; it executes immediately. Both work on the standalone emergency page (`/emergency.html`), which needs only a public node and your wallet. Download the memo with the wallet and module addresses from the security tab and keep it with the recovery key. ## What Invoise cannot do Invoise cannot move funds without your signature, cannot read your code or password, cannot open your envelopes without the signer's per-wallet key, and cannot stop the delegate path. --- # Concepts How shops, invoices, deposits and payouts work. Source: https://docs.invoise.me/start/concepts/ Invoise gives your customer a payment address and sends the received funds to your wallet, minus the service fee. | Concept | Meaning | | --- | --- | | Account | A user who signs in to the platform and receives access to merchants and shops. | | Merchant | A business in Invoise with its own shops, team and terms. | | Shop | Where invoices and deposits are created. It has its own recipient settings, API keys and operation history. | | Invoice | A request for a one-time payment of a fixed amount. Created through `/invoices`. | | Deposit | A reusable address for top-ups without a fixed amount. Created through `/deposits`. | | Incoming transfer | Funds sent by the payer to an invoice or deposit address. Several transfers can pay one invoice. | | Payout | Received funds transferred to the shop's recipient, minus the service fee. | `issuance` is the API resource name shared by invoices and deposits. Save the creation response's `id` as `issuance_id` for subsequent requests. Link an order in your own system to an invoice through `external_id`. The Invoise entity is always an invoice. ## How a payment works 1. Create an invoice or deposit. 2. Wait for its `address`, then give the customer the returned `payment_url`. 3. The customer sends the selected token on the selected network. 4. Invoise confirms the incoming transfer. Once enough funds are available, it sends the payout to your recipient. You can check progress with a GET request or receive webhooks. See [Invoice and deposit status](https://docs.invoise.me/payments/status/). An invoice becomes `funded` when enough confirmed funds have arrived. Payout completion is confirmed separately by `payout.confirmed`. ## Where payouts go | Setting | Purpose | | --- | --- | | Recipient address | The wallet that receives EVM payouts. Check that it supports your selected networks. | | Invoise wallet | Instead of an address, a shop can pay into the [Invoise wallet](https://docs.invoise.me/payments/wallet/) of an account member. | | Delegate | An optional wallet you control that can settle EVM invoices independently and pay for gas. An exchange address is not suitable. | You can change the recipient, the delegate and the choice between an address and the Invoise wallet later with `PATCH /shops/{shop_id}`. Every invoice and deposit keeps the recipient and delegate it was created with, so a change reaches only invoices and addresses created afterwards. Solana and Tron have their own recipients. See [Solana and Tron](https://docs.invoise.me/payments/solana-and-tron/). ## Fees and small payments The service fee is deducted from the payout. Read your shop's current [fees and limits](https://docs.invoise.me/payments/networks/); do not calculate them from a copied price list. For deposits, small payments accumulate until the payout threshold is reached. For invoices, partial payments accumulate until the invoice amount is reached. An invoice overpayment goes to Invoise with the fee, not to your recipient. Use a [sandbox shop](https://docs.invoise.me/payments/sandbox/) to try the integration without real funds. --- # Quickstart Create a shop, issue an invoice and receive the payment with the Invoise REST API. Source: https://docs.invoise.me/start/quickstart/ **Get started faster with an agent** Copy this prompt into your agent to set up a shop and check your first invoice. ```text wrap Help me integrate Invoise into my project. Read https://docs.invoise.me/llms.txt, https://docs.invoise.me/openapi.json and https://docs.invoise.me/agents/integration.md. Use my existing access or help me sign in and create a merchant, shop and API key. If you sign in with a wallet, ask me for my email before creating the merchant. Ask me for the recipient address before creating the shop. Read available networks, tokens, decimals and fees from the API. Start in Sandbox: create an invoice, get payment_url, connect webhooks with signature verification and deduplication, and check payment through GET. Save Idempotency-Key before sending requests. Finish with the test result and what is needed to accept real payments. ``` This is the normal way to integrate Invoise into a site or a backend: sign in as a person, set the shop up in the dashboard, then let your server call the API with a key. Building an unattended agent that has no browser and no email? Go to [Agent sign-in](https://docs.invoise.me/agents/sign-in/) instead. This page assumes a human does the setup. ## 1. Sign in and create a shop Open [platform.invoise.me](https://platform.invoise.me) and sign in with an email code or Google. On first sign-in you create a merchant; a new merchant has no shops yet, so create one. A shop needs a **recipient** — the wallet that receives your money — or pays into your [Invoise wallet](https://docs.invoise.me/payments/wallet/). You can change the recipient and the optional delegate later; invoices and addresses already created keep the ones they were created with. Create a sandbox shop if you want to rehearse without real funds. See [Sandbox](https://docs.invoise.me/payments/sandbox/). ## 2. Create an API key In the shop, create a key with `read` and `write` scopes. It starts with `ivk_` and is shown once, so save it right away. Your server sends it on every request: ```text Authorization: Bearer ivk_... ``` The key works for one shop only and cannot touch your account or your team. See [API keys](https://docs.invoise.me/integration/api-keys/). ## 3. Pick a network and token ```bash curl https://platform.invoise.me/api/v1/networks ``` Take `chain_id`, token address and `decimals` from the response. Choose a network enabled for your merchant and a ready token. Available assets change; do not hardcode their list. See [Networks and tokens](https://docs.invoise.me/payments/networks/). ## 4. Issue an invoice Amounts are integer strings in the token's smallest units. At 6 decimals, `10000000` is 10 tokens. Set these environment variables before the request: | Variable | Value | | --- | --- | | `CHAIN_ID` | The selected network’s `chain_id` from the API. | | `TOKEN_ADDRESS` | The selected token’s `tokens[].address` on that network. | | `AMOUNT` | Invoice amount in the smallest units, calculated using `tokens[].decimals`. | ```bash curl -X POST https://platform.invoise.me/api/v1/shops/{shop_id}/invoices \ -H 'Authorization: Bearer ivk_...' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: 2a8b1f47-6d0c-4a1f-8f0b-4f2c9f3a77d2' \ -d "{\"chain_id\":${CHAIN_ID:?},\"token\":\"${TOKEN_ADDRESS:?}\",\"amount\":\"${AMOUNT:?}\"}" ``` Replace `{shop_id}` with your shop ID. Generate and save a unique `Idempotency-Key` for this invoice; the sample key is only an example. The response is `202 Accepted`: save its `id` as `issuance_id`. `202` means creation has been accepted, not that the customer has paid. Want a reusable top-up address with no fixed amount? Call `/deposits` instead. See [Deposits and invoices](https://docs.invoise.me/payments/deposits-and-invoices/). ## 5. Wait for the address ```bash curl https://platform.invoise.me/api/v1/shops/{shop_id}/issuances/{issuance_id} \ -H 'Authorization: Bearer ivk_...' ``` Poll until an address comes back, then send the payer to the `payment_url` from that response. That is the [hosted checkout](https://docs.invoise.me/payments/checkout/), with the address, a QR code and live status already built. ## 6. Check whether it is paid **An invoice is paid when its `status` is `funded` or `settled`.** `funded` means the required amount is confirmed; `settled` means the invoice has closed on chain. Read the status through the API and receive change notifications through webhooks. ### Through a webhook Before your first payment, register your server's endpoint: ```bash curl -X POST 'https://platform.invoise.me/api/v1/shops/{shop_id}/webhooks' \ -H 'Authorization: Bearer ivk_...' \ -H 'Idempotency-Key: ' \ -H 'Content-Type: application/json' \ -d '{"url":"https://example.com/invoise","filters":["transfer","invoice","payout"]}' ``` Replace the URL with your public HTTPS endpoint and save `secret` from the response. 1. Verify `Invoise-Signature` with that secret and deduplicate by `Invoise-Event-ID`. See the [signature verification example](https://docs.invoise.me/integration/webhooks/#3-verify-before-processing). 2. `transfer.confirmed` reports a confirmed incoming transfer. Take `data.issuance_id` and read the invoice through the GET below: a single transfer may cover only part of the amount. 3. If `status` is `funded` or `settled` and `cancelled_at` is `null`, mark the order paid once. Separately, `payout.confirmed` confirms complete settlement accounting for the recipient payout. ### Through the API Read the invoice from your backend: ```bash curl 'https://platform.invoise.me/api/v1/shops/{shop_id}/issuances/{issuance_id}' \ -H 'Authorization: Bearer ivk_...' ``` Relevant response fields after payment: ```json { "status": "funded", "amount": "10000000", "received": "10000000", "cancelled_at": null } ``` The full invoice amount is confirmed. Mark the order paid; another GET or webhook must not credit it twice. For `open`, keep waiting: the full payment has not arrived yet. You can poll every 3–5 seconds and slow down for long waits. `registering` and `reconciling` do not mean success either. If `cancelled_at` is set, handle the paid, cancelled order separately. See [Invoice and deposit status](https://docs.invoise.me/payments/status/) for all states and deposit-specific behaviour. ## One rule to remember Creating an invoice or deposit requires `Idempotency-Key`. Keep the key around: if a request times out and you are unsure whether it worked, send the same request with the same key again. You will get the original result instead of a second invoice. See [Idempotency and errors](https://docs.invoise.me/integration/idempotency-and-errors/).