Tracking Orders

How to poll the Magma API for order status updates. Recommended cadence, terminal-state detection, and a complete reference polling loop.

Magma doesn't push order-status webhooks today. To follow an order from creation to confirmed channel, your integration polls the get_order query and watches for terminal states.

This page is the recipe.

For payments (not order) webhooks (e.g. payment.completed on a Lightning invoice), see the Amboss Payments docs. The Payments product and Magma marketplace are separate APIs.


The query

Authenticate as the order's buyer (API key or session key - see Authentication) and call user.market.orders.get_order:

query GetOrder($orderId: String!) {
  user {
    market {
      orders {
        get_order(order_id: $orderId) {
          id
          status
          payment_status
          channel_id
          transaction_id
          timeout
          blocks_until_can_be_closed
          confirmations { confirmations }
          fees {
            buyer { sats usd }
          }
        }
      }
    }
  }
}
{ "orderId": "ec562479-a4b8-44f4-95b4-150b310832de" }

Example response

{
  "data": {
    "user": {
      "market": {
        "orders": {
          "get_order": {
            "id": "ec562479-a4b8-44f4-95b4-150b310832de",
            "status": "WAITING_FOR_ON_CHAIN_CONFIRMATION",
            "payment_status": "SUCCESSFUL_PAYMENT",
            "channel_id": null,
            "transaction_id": "5e8a3f...c4f1",
            "timeout": "2026-06-10T18:42:00Z",
            "blocks_until_can_be_closed": 4320,
            "confirmations": { "confirmations": 2 },
            "fees": { "buyer": { "sats": "12500", "usd": "5.62" } }
          }
        }
      }
    }
  }
}

Terminal-state detection

A reliable polling loop watches for the terminal statuses (which never change after they're reached). Hard-coding the in-flight list is brittle - new in-flight states can be added; the terminal set is closed.

export const SUCCESS_STATUSES = new Set([
  "VALID_CHANNEL_OPENING",
  "CHANNEL_MONITORING_FINISHED",
]);

export const FAILURE_STATUSES = new Set([
  "SELLER_REJECTED",
  "SELLER_FAILED_TO_REACT",
  "BUYER_REJECTED",
  "BUYER_FAILED_TO_PAY",
  "SELLER_FAILED_TO_OPEN_CHANNEL",
  "SELLER_FAILED_TO_SEND_SWAP",
  "INVALID_CHANNEL_OPENING",
  "ADMIN_CLOSED",
]);

export const TERMINAL_STATUSES = new Set([
  ...SUCCESS_STATUSES,
  ...FAILURE_STATUSES,
]);

See Order Lifecycle for the full status reference.


Each phase has a different expected duration. Adapt the interval so you're not hammering the API while a 6-block confirmation runs.

PhaseTypical waitPoll interval
WAITING_FOR_SELLER_APPROVALSeconds to minutes5–10 s
WAITING_FOR_BUYER_PAYMENTUntil paid (or HODL expiry)5–10 s
WAITING_FOR_CHANNEL_OPENSeconds5–10 s
SELLER_SENT_TRANSACTIONMempool propagation15–30 s
WAITING_FOR_ON_CHAIN_CONFIRMATION~10 min per block30–60 s
ON_CHAIN_CONFIRMATIONSELLER_OPENED_CHANNELSeconds15–30 s

Don't poll faster than 1 request per 5 seconds for an individual order. Aggressive polling will get throttled.


Reference polling loop

A complete Node.js example that adapts the interval to the order's current phase:

import { GraphQLClient, gql } from "graphql-request";

const magma = new GraphQLClient("https://magma.amboss.tech/graphql", {
  headers: { Authorization: `Bearer ${process.env.AMBOSS_API_KEY}` },
});

const GET_ORDER = gql`
  query GetOrder($orderId: String!) {
    user {
      market {
        orders {
          get_order(order_id: $orderId) {
            id
            status
            payment_status
            channel_id
            confirmations { confirmations }
          }
        }
      }
    }
  }
`;

const SUCCESS = new Set(["VALID_CHANNEL_OPENING", "CHANNEL_MONITORING_FINISHED"]);
const FAILURE = new Set([
  "SELLER_REJECTED", "SELLER_FAILED_TO_REACT",
  "BUYER_REJECTED", "BUYER_FAILED_TO_PAY",
  "SELLER_FAILED_TO_OPEN_CHANNEL", "SELLER_FAILED_TO_SEND_SWAP",
  "INVALID_CHANNEL_OPENING", "ADMIN_CLOSED",
]);

const FAST_PHASES = new Set([
  "WAITING_FOR_SELLER_APPROVAL",
  "WAITING_FOR_BUYER_PAYMENT",
  "WAITING_FOR_CHANNEL_OPEN",
]);

const SLOW_PHASES = new Set([
  "SELLER_SENT_TRANSACTION",
  "WAITING_FOR_ON_CHAIN_CONFIRMATION",
  "ON_CHAIN_CONFIRMATION",
  "SELLER_OPENED_CHANNEL",
]);

async function trackOrder(orderId: string, maxWaitMs = 2 * 60 * 60 * 1000) {
  const start = Date.now();

  while (Date.now() - start < maxWaitMs) {
    const { user } = await magma.request(GET_ORDER, { orderId });
    const order = user.market.orders.get_order;

    console.log(
      `[${new Date().toISOString()}] ${order.status}` +
        (order.confirmations
          ? ` · ${order.confirmations.confirmations} confs`
          : ""),
    );

    if (SUCCESS.has(order.status)) return { ok: true, order };
    if (FAILURE.has(order.status)) return { ok: false, order };

    const intervalMs = FAST_PHASES.has(order.status)
      ? 7_500
      : SLOW_PHASES.has(order.status)
        ? 45_000
        : 10_000;

    await new Promise((r) => setTimeout(r, intervalMs));
  }

  throw new Error(`Order ${orderId} did not reach terminal state in time`);
}

const result = await trackOrder("ec562479-a4b8-44f4-95b4-150b310832de");
console.log(result.ok ? "✅ channel opened" : `❌ ${result.order.status}`);

What to watch beyond status

A few other fields give you progress signals without changing the top-level status:

FieldUseful for
payment_statusDetecting payment-leg failures (e.g. HODL_INVOICE_TIMEOUT) before the status reflects them.
transaction_idBecomes non-null once the seller broadcasts the funding TX.
confirmations.confirmationsBlock confirmation count for the funding TX.
channel_idShort channel ID. Becomes non-null once the channel is announced.
timeoutISO timestamp of when the current phase will expire. Useful for surfacing a countdown to your users.

Listing recent orders

If you don't have a specific order_id and just want everything recent:

query ListPurchases {
  user {
    market {
      orders {
        purchases(page: { limit: 20, offset: 0 }) {
          list {
            id
            status
            created_at
            amount { satoshi { sats usd } }
          }
          total
        }
      }
    }
  }
}

Swap purchases for sales if you're the seller side.


Next steps