Metadata-Version: 2.4
Name: invoq
Version: 0.3.0
Summary: Python SDK package for invoq server APIs and webhook verification.
Author: invoq
License-Expression: MIT
Project-URL: Homepage, https://invoq.money
Project-URL: Source, https://github.com/invoqmoney/sdk-python
Project-URL: Issues, https://github.com/invoqmoney/sdk-python/issues
Keywords: invoq,stablecoin,payments,sdk
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Office/Business :: Financial
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# invoq Python SDK

**English** · [Bahasa Indonesia](https://github.com/invoqmoney/sdk-python/blob/main/docs/README.id.md) · [Español](https://github.com/invoqmoney/sdk-python/blob/main/docs/README.es-419.md) · [Français](https://github.com/invoqmoney/sdk-python/blob/main/docs/README.fr.md) · [Português](https://github.com/invoqmoney/sdk-python/blob/main/docs/README.pt-BR.md) · [Tiếng Việt](https://github.com/invoqmoney/sdk-python/blob/main/docs/README.vi.md) · [Türkçe](https://github.com/invoqmoney/sdk-python/blob/main/docs/README.tr.md) · [ไทย](https://github.com/invoqmoney/sdk-python/blob/main/docs/README.th.md) · [简体中文](https://github.com/invoqmoney/sdk-python/blob/main/docs/README.zh-Hans.md) · [繁體中文](https://github.com/invoqmoney/sdk-python/blob/main/docs/README.zh-Hant.md)

Python SDK for invoq server APIs and webhook verification. Create stablecoin
invoices from your backend and fulfill orders with signed webhooks.

Use this package only on your server. It accepts secret keys and must not be
bundled into browser code.

**Coding with AI? Paste this.**

```
Add stablecoin payments to my project with invoq. Start in test mode. Read the docs before you write any code: https://invoq.money/llms.txt
```

## Server SDKs

Create invoices and verify webhooks from your backend in any of these languages — same REST API, same webhook signature. This repository is the Python SDK.

| Language | Repository |
| --- | --- |
| Node.js | [github.com/invoqmoney/sdk-js](https://github.com/invoqmoney/sdk-js) (`@invoq/server`) |
| Python | **this repo** |
| PHP | [github.com/invoqmoney/sdk-php](https://github.com/invoqmoney/sdk-php) |
| Go | [github.com/invoqmoney/sdk-go](https://github.com/invoqmoney/sdk-go) |
| Rust | [github.com/invoqmoney/sdk-rust](https://github.com/invoqmoney/sdk-rust) |
| Ruby | [github.com/invoqmoney/sdk-ruby](https://github.com/invoqmoney/sdk-ruby) |

The browser side is the same for every backend: **`@invoq/checkout`** (JavaScript, in [github.com/invoqmoney/sdk-js](https://github.com/invoqmoney/sdk-js)) opens the in-page checkout modal for any frontend.

## Installation

```bash
python -m pip install invoq
```

Requires Python 3.9 or newer.

## Get your keys

1. Sign in to the invoq dashboard and create a project.
2. On the API keys page, create a secret key. Test keys start with `sk_test_`,
   live keys with `sk_live_`.
3. In your project's webhooks settings, save your webhook URL and store the
   webhook secret (`whsec_...`) for that mode.
4. Set up your Receiving wallet before going live. Test invoices don't need
   one; a live invoice with nowhere to settle fails with
   `409 no_payment_options_available`.

Add both to your server environment:

```bash
INVOQ_SECRET_KEY=sk_test_...
INVOQ_WEBHOOK_SECRET=whsec_...
```

Start with test keys. Switch to the live key and live webhook secret when you go
to production.

## Create a client

```python
import os

from invoq import Invoq

invoq = Invoq(os.environ["INVOQ_SECRET_KEY"])
```

Production API default:

```text
https://api.invoq.money
```

Override the API origin and request timeout during development:

```python
invoq = Invoq(
    os.environ["INVOQ_SECRET_KEY"],
    api_origin="http://localhost:8787",
    timeout_ms=10_000,
)
```

`api_origin` must be an absolute `http` or `https` origin with no path, query,
hash, username, or password. The SDK appends `/v1/...` API paths.

`timeout_ms` defaults to 10 seconds and bounds each socket operation, not the
request as a whole: a server that keeps sending bytes slowly, without ever
pausing for that long, can hold a call open past it. Wrap the call in your own
deadline if you need a hard ceiling.

## Invoices

Create an invoice:

```python
invoice = invoq.invoices.create({
    "amount": "149",
    "description": "SaaS boilerplate",
    "reference_id": "order_1234",
    "return_url": "https://merchant.example/thanks",
})
```

Notes:

- Use a server-side amount. Do not trust client-supplied amounts.
- `amount` is a decimal USD string from `"0.01"` to `"1000000.00"` with up to 2
  decimal places, such as `"129"` or `"129.99"`. Currency is always USD, and
  test or live comes from the key — neither is a request field.
- Use `reference_id` to map `invoice.paid` webhooks back to your order. It also
  makes creation retry-safe: creating again with the same `reference_id` and the
  same invoice terms returns the existing invoice instead of a duplicate, while
  different terms fail with a `409 reference_id_conflict` API error.
- Omit `return_url` to use the project's default return URL. Pass `None` to send
  JSON `null` and create the invoice without a return URL. On `reference_id`
  retries, pass `return_url` explicitly when you need to assert a specific value.
- `description` and `reference_id` must be strings when present.

Get an invoice:

```python
invoice = invoq.invoices.get("inv_123")
```

`invoices.get()` returns the public invoice shape used by the hosted checkout
endpoint: the create shape plus `amount_paid`, `project`, and `transfers`,
minus `reference_id`. Use the create response or the `invoice.paid` webhook when
you need your merchant `reference_id`.

Create a test payment:

```python
paid_invoice = invoq.invoices.create_test_payment("inv_123", {
    "amount": "149",
    "reference_id": "test_payment_001",
})
```

`create_test_payment` only works on invoices created with a `sk_test_` key. When
payments reach the invoice amount, the invoice becomes `paid` and invoq sends a
real signed `invoice.paid` webhook to your test webhook URL. Partial amounts are
allowed and produce `partially_paid`.

Two status fields. `status` is the accounting one — `unpaid`, `partially_paid`,
`paid`, `settling`, `settled`, `review_required` — where the three paid-like
values differ only in how far the funds have moved to your wallet.
`checkout_status` is payer-facing — `open`, `confirming`, `expired`, `paid`,
`unavailable` — and never authorizes fulfillment. `payment_revision` increments
whenever the confirmed payment set changes, so you can discard a snapshot older
than one you already hold.

Amounts in responses are normalized to 4 decimal places: create with `"129"` and
the invoice returns `amount: "129.0000"`. Compare amounts numerically, not as
strings. `amount_due` is derived as `max(amount - amount_paid, 0)` and uses the
same 18-decimal scale as `amount_paid`; `amount_overpaid` is its mirror,
`max(amount_paid - amount, 0)`, so you never subtract money yourself.

`payment_options` holds the payment instructions, fixed at creation and `[]` in
test mode. Entries are discriminated by `status`, then `collection_method`: only
`"ready"` is payable, `"evm_deposit"` carries `deposit_address` and
`suggested_amount`, `"direct_exact"` carries `recipient_address` and an
`exact_amount` the buyer must send to the digit. `transfers` is the confirmed
receipt trail — `transaction_id`, `event_index`, `amount`,
`explorer_transaction_url` — and stays `[]` until a payment confirms. Full field
reference: [REST API docs](https://github.com/invoqmoney/api).

The SDK returns the response `data` object directly.

## Hosted checkout page

Every invoice also has a hosted checkout page at:

```text
https://pay.invoq.money/<invoice id>
```

Share the link or redirect to it when an in-page checkout modal is not a fit.

## Webhooks

Pass the raw request body to `verify_webhook`. Do not parse JSON and re-dump it
before verification.

```python
import os

from invoq import is_invoice_paid, is_invoice_payment_reversed, verify_webhook

event = verify_webhook(
    raw_body,
    {"invoq-signature": signature_header},
    os.environ["INVOQ_WEBHOOK_SECRET"],
)

if is_invoice_paid(event):
    order_id = event["data"]["invoice"]["reference_id"]

    if order_id is None:
        raise ValueError("Missing invoice reference_id for fulfillment.")

    fulfill_order(order_id)

elif is_invoice_payment_reversed(event):
    hold_order(event["data"]["invoice"]["reference_id"])
```

Use `invoice.paid` webhooks to fulfill orders on your server. When
`is_invoice_paid(event)` is true, the invoice is ready for automatic
fulfillment; use the invoice `reference_id` to find and fulfill your order. The
helper accepts paid-equivalent invoice statuses (`paid`, `settling`, or
`settled`) and rejects `review_required`.

invoq also sends `invoice.payment_reversed` when a previously paid invoice drops
back below its amount — a chain reorg dropping a confirmed transfer, for
example. Catch it with `is_invoice_payment_reversed(event)` and hold or reverse
the fulfillment according to your own policy. Unlike the paid helper, it does
not check the invoice status: dropping a reversal would leave an order fulfilled
on a payment that no longer exists. An event type this SDK version does not
model still verifies and is returned as-is.

Important:

- Pass the raw request body as a string or bytes.
- Pass a header mapping with the `invoq-signature` header.
- Do not parse JSON and re-dump it before verification.
- `verify_webhook` does not require `Invoq(...)` or your invoq API secret key.
- Use your webhook secret, not `INVOQ_SECRET_KEY`.
- Fulfill idempotently. Failed deliveries are retried — up to 5 attempts,
  backing off 1 minute, 5 minutes, 30 minutes, then 2 hours — so your endpoint
  can receive the same event more than once. Deliveries can also arrive out of
  order: keep the snapshot with the highest `payment_revision`.
- Respond with a 2xx quickly. Any other status counts as a failed delivery and
  is retried, including redirects and `4xx`, so a deploy window or a temporarily
  misrouted path is retried rather than dropped.

Webhook verification failures raise `InvoqSignatureVerificationError`. The
signature header is:

```text
invoq-signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">
```

## Errors

```python
from invoq import InvoqApiError, InvoqError

try:
    invoq.invoices.create({"amount": "0.001"})
except InvoqApiError as error:
    print(error.status)
    print(error.code)
    print(error.fields)
except InvoqError:
    raise
```

Non-2xx API responses raise `InvoqApiError`, which has `status`, `code`,
`fields`, `meta`, and the raw `payload`. Connection failures, timeouts, response
parse failures, and invalid SDK inputs raise `InvoqError`.
