Send Payments

Pay outbound Lightning invoices and Lightning Addresses programmatically from any Amboss wallet.

Pay an outbound Lightning invoice or Lightning Address from one of your wallets.

payment.transaction.create_send(input: CreateSendTransactionInput) → PaymentsTransaction

Two send paths

Exactly one of request or address must be provided:

PathFieldUse when
BOLT11 invoicerequest: { bolt11 }You have an invoice from the recipient. Amount and description are encoded in the invoice.
Lightning Addressaddress: { lightning_address, amount }You only have a Lightning Address ([email protected]). You specify how much to send.

Input fields

FieldTypeRequiredNotes
wallet_idUUIDyesThe wallet the funds come from
request.bolt11string (1 – 2048)one ofBOLT11 invoice string
address.lightning_addressstring (1 – 255)one of[email protected] format
address.amountstringwith addressPositive integer in the wallet asset's base unit
idempotency_keystring (1 – 255)noReplays return the original transaction
metadatastring (JSON, ≤2048)noSender-side annotation. The receiver-visible description lives on the BOLT11.

Pay a BOLT11 invoice

mutation SendBolt11 {
  payment {
    transaction {
      create_send(input: {
        wallet_id: "5e4b1e2a-9f3c-4a5b-8c7d-1234567890ab"
        request: { bolt11: "lnbc100u1p3xxxxxxxx..." }
        idempotency_key: "payout-2026-06-01-42"
        metadata: "{\"payout_id\":\"42\"}"
      }) {
        id
        status
        payment_hash
        amount { full_amount }
      }
    }
  }
}
curl -X POST https://rails.amboss.tech/graphql \
  -H "Content-Type: application/json" \
  -H "x-api-key: $AMBOSS_API_KEY" \
  -d '{
    "query": "mutation($input: CreateSendTransactionInput!) { payment { transaction { create_send(input: $input) { id status payment_hash } } } }",
    "variables": {
      "input": {
        "wallet_id": "5e4b1e2a-9f3c-4a5b-8c7d-1234567890ab",
        "request": { "bolt11": "lnbc100u1p3xxxxxxxx..." },
        "idempotency_key": "payout-2026-06-01-42"
      }
    }
  }'
import { GraphQLClient, gql } from "graphql-request";

const client = new GraphQLClient("https://rails.amboss.tech/graphql", {
  headers: { "x-api-key": process.env.AMBOSS_API_KEY },
});

const CREATE_SEND = gql`
  mutation CreateSend($input: CreateSendTransactionInput!) {
    payment {
      transaction {
        create_send(input: $input) {
          id
          status
          payment_hash
        }
      }
    }
  }
`;

const { payment } = await client.request(CREATE_SEND, {
  input: {
    wallet_id: walletId,
    request: { bolt11: invoiceString },
    idempotency_key: `payout-${payoutId}`,
    metadata: JSON.stringify({ payout_id: payoutId }),
  },
});

Pay a Lightning Address

mutation SendToAddress {
  payment {
    transaction {
      create_send(input: {
        wallet_id: "5e4b1e2a-9f3c-4a5b-8c7d-1234567890ab"
        address: {
          lightning_address: "[email protected]"
          amount: "50000"
        }
        idempotency_key: "tip-2026-06-01-alice"
      }) {
        id
        status
      }
    }
  }
}
await client.request(CREATE_SEND, {
  input: {
    wallet_id: walletId,
    address: {
      lightning_address: "[email protected]",
      amount: "50000", // base units
    },
    idempotency_key: `tip-${tipId}`,
  },
});

Lightning Address sends from Taproot Asset wallets are not yet supported. Use a BTC wallet for Lightning Address payouts, or send to a BOLT11 from a stablecoin wallet.

Network constraints

  • Live wallets only accept invoices for the production network (mainnet lnbc…). Invoices for testnet, mutinynet, or regtest are rejected with Invoice network … is not allowed for this wallet.
  • Sandbox wallets accept invoices on any network — useful for testing against your own infrastructure or public testnets.
  • Amountless invoices are not yet supported. The BOLT11 must encode a positive amount.
  • Expired invoices are rejected up front (Invoice has already expired).
  • The wallet must have an attached node for the asset type. For Taproot Assets the node must have tapd capability.

Lifecycle

Send transactions follow the same status machine as receive:

PENDING → COMPLETED | FAILED

The payment.completed and payment.failed webhooks fire on terminal transitions. The data.direction field on the envelope is "send". See Webhooks.

Inspecting a send

When a send terminates as FAILED, the failure reason is on the transaction itself — query error and events for the full picture:

query GetSend($id: String!) {
  payment {
    transaction {
      find_one(id: $id) {
        id
        status
        settle_amount { full_amount }
        settled_at
        exchange_rate
        error
        events {
          event_type
          message
          details
          created_at
        }
      }
    }
  }
}
  • error is null on success and populated with a human-readable reason on FAILED (e.g. routing failure, insufficient liquidity, invoice already paid).
  • events is the ordered state-transition log. details carries provider-specific context (route attempts, HTLC failures); use it when filing a support ticket.
  • exchange_rate is set for Taproot Asset sends once settled, null for BTC.

Don't use settle_amount_sats. It is deprecated in favor of settle_amount, which carries both the value and the asset metadata. The deprecated field will be removed in a future release.

Idempotency

idempotency_key is highly recommended for sends — a retry without a key can result in a double payment. Replays with the same (wallet_id, idempotency_key) return the original transaction.

Concurrent requests with the same key are serialized:

A request with this idempotency_key is already in progress

If you omit idempotency_key, the server generates one internally per call (so each call is unique by definition). The safer pattern is for you to choose one tied to your business identifier (payout-${id}).

Sandbox testing

Set metadata.amb_sandbox_behavior to drive terminal state in sandbox:

{
  wallet_id: walletId,
  request: { bolt11: anySandboxInvoice },
  metadata: JSON.stringify({ amb_sandbox_behavior: "complete" }),
}

completepayment.completed, failpayment.failed. See Environments for the full table.

Next steps