Receive Payments

Receive Bitcoin and stablecoin payments via Lightning. Generate BOLT11 invoices, render QR codes, and track status through webhooks.

Generate a Lightning invoice the payer can pay. The mutation returns a BOLT11 payment_request you render as a QR code or lightning: link, plus a transaction record whose status you can track via webhooks.

payment.transaction.create_receive(input: CreateReceiveTransactionInput) → PaymentsTransaction

Input fields

FieldTypeRequiredNotes
wallet_idUUIDyesThe wallet that will receive the funds
amountstringyesPositive integer in the wallet asset's base unit (sats for BTC, micro-units for stablecoins)
descriptionstring (≤640 chars)noEncoded in the BOLT11; visible to the payer
expires_in_secondsint (1 – 2,592,000)noInvoice expiry. Defaults to 10 minutes
idempotency_keystring (1 – 255 chars)noReplays return the original invoice instead of creating a new one
metadatastring (JSON, ≤2048 chars)noApplication metadata; round-tripped on webhooks

Create an invoice

mutation CreateReceive {
  payment {
    transaction {
      create_receive(input: {
        wallet_id: "5e4b1e2a-9f3c-4a5b-8c7d-1234567890ab"
        amount: "100000"
        description: "Order #42"
        expires_in_seconds: 3600
        idempotency_key: "order-42-attempt-1"
        metadata: "{\"order_id\":\"42\"}"
      }) {
        id
        status
        payment_request
        payment_hash
        expires_at
        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: CreateReceiveTransactionInput!) { payment { transaction { create_receive(input: $input) { id status payment_request payment_hash expires_at amount { full_amount } } } } }",
    "variables": {
      "input": {
        "wallet_id": "5e4b1e2a-9f3c-4a5b-8c7d-1234567890ab",
        "amount": "100000",
        "description": "Order #42",
        "expires_in_seconds": 3600,
        "idempotency_key": "order-42-attempt-1",
        "metadata": "{\"order_id\":\"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_RECEIVE = gql`
  mutation CreateReceive($input: CreateReceiveTransactionInput!) {
    payment {
      transaction {
        create_receive(input: $input) {
          id
          status
          payment_request
          payment_hash
          expires_at
        }
      }
    }
  }
`;

const { payment } = await client.request(CREATE_RECEIVE, {
  input: {
    wallet_id: walletId,
    amount: "100000",
    description: `Order #${orderId}`,
    expires_in_seconds: 3600,
    idempotency_key: `order-${orderId}-attempt-${attempt}`,
    metadata: JSON.stringify({ order_id: orderId }),
  },
});

// Show payment.transaction.create_receive.payment_request to the customer

Example response:

{
  "data": {
    "payment": {
      "transaction": {
        "create_receive": {
          "id": "tx_01HX9YQK7P8MVZ3FN4G2RWS6CD",
          "status": "PENDING",
          "payment_request": "lnbc1m1p0...",
          "payment_hash": "3b6e7d...",
          "expires_at": "2026-06-02T13:30:00.000Z",
          "amount": { "full_amount": "100000" }
        }
      }
    }
  }
}

Render payment_request as a QR code (or a lightning: link). Once paid, the transaction moves to COMPLETED and a payment.completed webhook fires.

Lifecycle

Each transition emits the matching webhook: payment.completed, payment.expired, payment.failed. See Webhooks for the payload shape.

Idempotency

If you set idempotency_key, replaying the same (wallet_id, idempotency_key) pair returns the original invoice rather than creating a new one. This makes it safe to retry from your side without orphaning invoices.

Concurrent requests with the same key are serialized; if a second call arrives while the first is still in-flight, it returns:

A request with this idempotency_key is already in progress

Sandbox testing

In sandbox environments, drive deterministic state transitions via the metadata flag:

{
  amount: "100000",
  wallet_id: walletId,
  metadata: JSON.stringify({
    amb_sandbox_behavior: "complete", // 'complete' | 'fail' | 'expire'
    order_id: orderId,
  }),
}
  • completepayment.completed fires after a short delay
  • failpayment.failed
  • expire (default) → invoice expires naturally

Use this in integration tests to exercise every branch without a live Lightning node.

Inspecting a transaction

Webhooks are the recommended path, but you can also poll a transaction directly. find_one returns the full PaymentsTransaction including the event timeline, exchange rate, and the failure reason when applicable:

query GetTransaction($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
        }
      }
    }
  }
}
FieldWhen populatedNotes
errorstatus: FAILEDHuman-readable reason the payment terminated. null on success.
eventsalwaysOrdered timeline of state transitions. event_type mirrors the webhook (payment.pending, payment.completed, …). details is a JSON string with route/provider-specific context. Useful for support tickets and post-mortems.
exchange_rateTaproot Asset walletsThe asset→sats rate used at settlement, as a decimal string. null for BTC and null until settlement for assets.
settle_amountstatus: COMPLETEDUse this. Pair with full_amount to render the settled value.

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.

Listing transactions

Use payment.transaction.find_many for history views, reconciliation, or pagination. wallet_id and environment_id are mutually exclusive scopes; filter narrows by direction, status, or date range:

query ListTransactions {
  payment {
    transaction {
      find_many(input: {
        environment_id: "81b73615-ddf3-46e3-943a-467c3e442e04"
        filter: {
          direction: RECEIVE
          status: COMPLETED
          date_range: { from: "2026-06-01T00:00:00Z", to: "2026-06-30T23:59:59Z" }
        }
        page: { limit: 50, offset: 0 }
      }) {
        total_count
        pagination { limit offset }
        list {
          id
          status
          direction
          amount { full_amount }
          settled_at
          wallet_id
        }
      }
    }
  }
}

list returns SimplePaymentsTransaction — a subset of the full record. Fetch any single row with find_one(id) to get events, error, payment_hash, payment_request, and exchange_rate.

Next steps