Prompt for Agents
A copy-paste prompt that gives Claude Code, Codex, Cursor, or any coding agent everything it needs to integrate the @ambosstech/payments TypeScript SDK correctly.
Working with a coding agent? Paste the prompt below into Claude Code, Cursor, Codex, Copilot, or whatever you use. It carries the client setup, every method signature your integration needs, the typed error classes, and the handful of rules agents reliably get wrong — string amounts in minor units, the sandbox expire default, raw-body webhook verification, and idempotency on sends.
Integrate the TypeScript SDK
PromptWires up @ambosstech/payments end to end: client, wallet discovery, receive, send, verified webhooks, and typed errors.
# Task: integrate Amboss Payments (TypeScript SDK)
You are integrating Amboss Payments into this TypeScript codebase using the
official SDK, `@ambosstech/payments`. Amboss Payments settles USDT, USDC, and
BTC over the Lightning Network with sub-second finality and no chargebacks.
Docs: https://docs.amboss.tech/sdk
Work in the sandbox environment until the whole flow passes end to end. Do not
touch live keys or live wallets unless I explicitly ask you to.
## Rules you must not break
1. **Amounts are strings in the asset's minor units.** `"1000"` sats for BTC,
`"1000000"` for 1 USDT. Never a number, never a float.
2. **Never hard-code an `asset_id` or a precision.** Read it from
`wallet.asset.precision` at runtime.
3. **In sandbox, `metadata.amb_sandbox_behavior` decides the outcome and
defaults to `expire`.** Set `"complete"`, `"fail"`, or `"expire"`
deliberately, or your test invoice just times out.
4. **Verify every webhook over the raw request body**, before acting on it.
5. **Dedupe on `event.id`.** Delivery is at-least-once.
6. **Pass `idempotencyKey` on every send.** A retry without one can pay twice.
7. **Secrets stay in environment variables**, never in source, logs, or
fixtures. The team `password` in particular is used to decrypt the node
macaroon *in your own process* and is never sent to the API — keep it that
way.
## Install
```bash
pnpm add @ambosstech/payments
```
Use whichever package manager this repo already uses. `@ambosstech/core`
arrives transitively; nothing else to install. Requires Node.js 18.18+.
## Environment variables to add
Add these to the repo's env schema / `.env.example` using its existing
conventions. Never commit real values.
- `AMBOSS_API_KEY` — service API key (`amb_test_…` sandbox, `amb_live_…` live)
- `AMBOSS_WALLET_ID` — the wallet the integration receives into
- `AMBOSS_WEBHOOK_SECRET` — the endpoint secret used to verify deliveries
## Build this
### 1. One shared client
Construct the client once and export it from a single module. Do not
instantiate it per request.
```ts
import { Payments } from "@ambosstech/payments";
export const payments = new Payments({
serviceApiKey: process.env.AMBOSS_API_KEY,
webhookSecret: process.env.AMBOSS_WEBHOOK_SECRET,
timeoutMs: 30_000, // default
});
```
Both fields are optional: a service that only receives webhooks needs just
`webhookSecret`. Touching `payments.environments`, `payments.wallets`, or
`payments.transactions` without a `serviceApiKey` throws `ConfigError`.
`baseUrl` and `fetch` are overridable — inject `fetch` to stub the network
in tests rather than mocking modules.
If this repo has a DI container (NestJS, tsyringe, etc.), register the client
there and follow the existing provider conventions instead of exporting a
module-level singleton.
### 2. Wallet discovery
```ts
const environments = await payments.environments.list();
const wallets = await payments.wallets.list({ environmentId });
const wallet = await payments.wallets.get(walletId);
wallet.asset.precision; // use this for every amount conversion
wallet.balance;
```
`wallets.list` returns a trimmed record for fast listing. Call
`wallets.get(id)` when you need `balance`, `asset`, or attached `nodes`.
### 3. Receiving
```ts
const transaction = await payments.transactions.createReceive({
wallet_id: walletId,
amount: "1000",
description: "Order #1234",
expires_in_seconds: 3600,
idempotency_key: `order-${orderId}-attempt-${attempt}`,
metadata: { order_id: String(orderId) },
});
transaction.payment_request; // BOLT11 to render as a QR or lightning: link
transaction.payment_hash;
```
Note the shape difference from `send`: `createReceive` takes snake_case API
fields; `send` takes camelCase. Metadata here is a real object — the SDK
serializes it for you. It round-trips onto the webhook, so put your order id in
it and correlate on that.
### 4. Sending
```ts
const { transaction, payment } = await payments.transactions.send({
walletId,
password, // live only; decrypts the macaroon locally
destination: { bolt11: "lnbc1..." }, // or { lightningAddress, amountSats }
idempotencyKey: `payout-${payoutId}`,
onUpdate: ({ status }) => log(status), // INITIATED | IN_FLIGHT | SUCCEEDED | FAILED
signal: abortController.signal,
});
payment?.status; // 'SUCCEEDED' | 'FAILED' — null in sandbox
payment?.feeSat;
```
On live wallets `send` decrypts the node's admin macaroon in-process with the
team `password` and drives the payment directly against the node, resolving
with the terminal result. The SDK picks LND for base-asset wallets and litd for
Taproot Asset wallets automatically — do not wire that yourself.
**In sandbox there is no password and `payment` comes back `null`.** The
backend settles asynchronously per `metadata.amb_sandbox_behavior`. Observe
the outcome through webhooks; do not assume a null `payment` means failure.
### 5. The webhook handler
```ts
const event = payments.webhooks.verify({
payload: rawBody, // string | Buffer — the RAW body
signature: headers["x-webhook-signature"],
timestamp: headers["x-webhook-timestamp"],
toleranceSeconds: 300, // default
});
event.id; // dedupe on this
event.event_type; // payment.pending | completed | failed | expired
event.data.direction; // 'send' | 'receive'
event.data.status;
event.data.settle_amount; // credit this, not data.amount — the fee sits in between
event.data.metadata; // your round-tripped order_id
```
There is also a static form, `Payments.webhooks.verify({ secret, payload,
signature, timestamp })`, for stateless handlers with no client.
Capture the raw body **before any JSON parser runs** — Express:
`express.raw({ type: "application/json" })`; NestJS:
`NestFactory.create(AppModule, { rawBody: true })` then `req.rawBody`; Fetch:
`await request.text()`. A re-serialized body will not match the HMAC.
Respond `200` quickly and queue the real work. Failures are retried at 10s,
60s, then 10 minutes, up to 8 attempts.
### 6. Error handling
Branch on the typed classes, never on message strings:
```ts
import {
ApiError, // API returned an error; carries status + graphqlErrors
ConfigError, // SDK misconfigured, e.g. missing serviceApiKey
NetworkError, // never reached the API
WebhookVerificationError, // signature failed; carries a typed .code
DecryptionError, // wrong team password
PaymentSendError, // the node rejected or failed the payment
} from "@ambosstech/payments";
```
`WebhookVerificationError.code` is one of `missing_secret`,
`missing_signature`, `missing_timestamp`, `invalid_timestamp`,
`timestamp_out_of_tolerance`, `invalid_signature_format`,
`signature_mismatch`, `invalid_payload_json`. Map it to a 401 and log the
code, never the payload.
## Before you tell me you are done
- [ ] No API key, webhook secret, or team password in source, logs, tests, or
fixtures.
- [ ] Every amount is a string in minor units derived from
`wallet.asset.precision`.
- [ ] The webhook handler reads the raw body, calls `webhooks.verify`, and
dedupes on `event.id` before any side effect.
- [ ] Every send passes an `idempotencyKey`.
- [ ] Errors branch on the typed classes above, not on strings.
- [ ] A sandbox receive with `amb_sandbox_behavior: "complete"` settles and
the handler credits the order exactly once, including on a replayed
delivery.
- [ ] The repo's existing lint, typecheck, and test commands pass. Show me the
output.
Follow this repo's existing conventions for structure, naming, error handling,
and tests. Do not add dependencies the codebase does not already have.
## Reference
- SDK overview — https://docs.amboss.tech/sdk
- Configuration — https://docs.amboss.tech/sdk/configuration
- Environments and wallets — https://docs.amboss.tech/sdk/environments-and-wallets
- Transactions — https://docs.amboss.tech/sdk/transactions
- Webhooks — https://docs.amboss.tech/sdk/webhooks
- Errors — https://docs.amboss.tech/sdk/errors
- Underlying GraphQL API — https://docs.amboss.tech/payments
- Machine-readable index of all docs — https://docs.amboss.tech/llms.txt
Not on TypeScript? Use the Payments API prompt instead. It targets the raw GraphQL endpoint, which is callable from any language.
How to use it
Paste it into your agent
Copy the prompt and drop it into your agent's session, in the repo where the integration should land. It's written to be self-contained — no other page needs to be open.
Add your own context
Tell it what you're building ("add a payout worker that pays Lightning Addresses") and point it at the module that should own the integration. The prompt tells the agent to reuse your existing client, DI container, and error conventions rather than inventing new ones.
Keep it in sandbox
The prompt already instructs the agent to stay in sandbox and to set amb_sandbox_behavior deliberately. It also warns that sandbox sends resolve with a null payment — a detail agents otherwise read as a failure.
Walk the self-check
The prompt closes with a checklist — no leaked secrets, string amounts, verified webhooks, idempotent sends, typed error branching. Ask the agent to walk it item by item and show you the lint, typecheck, and test output.
Feeding docs to your agent
Everything on this site is also available as plain text, for agents that fetch rather than browse:
| Resource | What it is |
|---|---|
/prompts/payments-sdk.md | This prompt, as raw Markdown |
/prompts/payments-api.md | The GraphQL API prompt, as raw Markdown |
/llms.txt | A machine-readable index of every page on this site |
Point an agent at /llms.txt when it needs to look something up mid-task, and at a prompt when it needs to know how to build.
Review what your agent writes before you ship it. The prompt tells it to keep the API key, webhook secret, and team password in environment variables and out of source, but you own the final diff — check for leaked secrets, and confirm the webhook handler passes the raw body to webhooks.verify.
What the prompt covers
| Area | What the agent is told |
|---|---|
| Client | Construct Payments once and share it; serviceApiKey and webhookSecret are independently optional |
| Amounts | Strings in minor units, read from wallet.asset.precision — never a number |
| Receiving | transactions.createReceive takes snake_case fields and an object metadata that round-trips onto the webhook |
| Sending | transactions.send takes camelCase, decrypts the macaroon in-process on live, and returns a null payment in sandbox |
| Webhooks | Instance and static verify, raw-body capture per framework, event.id dedupe, settle_amount over amount |
| Errors | Branch on ApiError, ConfigError, NetworkError, WebhookVerificationError, DecryptionError, PaymentSendError — never on message strings |
Related
Webhooks
Verify Amboss Payments webhooks with the SDK. Instance and static APIs, raw-body handling for Express, NestJS, and Fetch, and typed error codes.
Errors
Typed error classes thrown by the Amboss Payments SDK: ApiError, ConfigError, NetworkError, WebhookVerificationError, DecryptionError, and PaymentSendError.