Gateway/Accept payments

Accept payments

Use the gateway from your backend to charge for an API operation. You need a running Phoenixd node on Nodana, a Nodana API key with gateway access, and a customer client that supports MPP or L402.

Request payment

When a customer requests a paid operation, call POST https://api.nodana.io/v1/gateway/payments. Send the receiving node, amount in satoshis, and a description.

Calculate the price in your backend before requesting payment. Serve free operations directly.

MPP is the default protocol. Omit the optional protocol field in the JSON body or set it explicitly to "mpp". Set "protocol": "l402" to use L402 instead.

MPP Example

This example requests 100 sats using the default MPP protocol. The API key needs gateway:write permission.

Request

curl "https://api.nodana.io/v1/gateway/payments" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "nodeId": "node-123",
    "amountSat": 100,
    "description": "API query"
  }'

Response

201 Created

{
  "id": "payment-123",
  "protocol": "mpp",
  "status": "payment_required",
  "challenge": "<MPP challenge returned by Nodana>"
}

L402 Example

Set protocol to "l402" to request 100 sats from an L402 client. The API key needs gateway:write permission.

Request

curl "https://api.nodana.io/v1/gateway/payments" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "nodeId": "node-123",
    "amountSat": 100,
    "description": "API query",
    "protocol": "l402"
  }'

Response

201 Created

{
  "id": "payment-123",
  "protocol": "l402",
  "status": "payment_required",
  "challenge": "L402 macaroon=\"<macaroon>\", invoice=\"<Lightning invoice>\""
}

Return the challenge

Nodana returns a payment ID and a payment challenge. Retain the ID with the customer request. Return HTTP 402 Payment Required to the calling agent and set the WWW-Authenticate header to the returned challenge value.

Keep enough context to associate the client's retry with the same payment and operation.

Use the same approach for MPP and L402. Your API chooses its response body and content type. Set Cache-Control: no-store to prevent the payment challenge from being cached.

For example, using the Web Response API:

return new Response(JSON.stringify({ error: "payment_required" }), {
  status: 402,
  headers: {
    "WWW-Authenticate": payment.challenge,
    "Content-Type": "application/json",
    "Cache-Control": "no-store",
  },
});

Validate the retry

When the client retries with payment credentials, call POST /v1/gateway/payments/{id}/validate with the original payment ID in the path and credentials in the JSON body. You do not need to resend the protocol: the payment retains the choice made at creation.

Request

Pass the full value of the customer's payment Authorization header as credential. Keep your Nodana API key in the bearer header of the request to Nodana.

curl "https://api.nodana.io/v1/gateway/payments/payment-123/validate" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "credential": "<customer payment Authorization header>"
  }'

Accepted response

200 OK

{
  "id": "payment-123",
  "protocol": "mpp",
  "status": "paid",
  "receipt": "<receipt returned by Nodana>"
}

For MPP, set the Payment-Receipt header on your API's successful response to the returned receipt value. L402 returns id, protocol, and status without a receipt. Your API chooses the response body.

Retrieve the challenge

To retrieve the challenge again, call GET /v1/gateway/payments/{id}/challenge with gateway:read permission:

curl "https://api.nodana.io/v1/gateway/payments/payment-123/challenge" \
  -H "Authorization: Bearer YOUR_API_KEY"

200 OK

{
  "id": "payment-123",
  "protocol": "mpp",
  "status": "payment_required",
  "challenge": "<MPP challenge returned by Nodana>"
}

Return this challenge to the customer in the WWW-Authenticate header of an HTTP 402 response. Retrieving the challenge does not authorise paid access.

An invalid credential returns 400 with code invalid_payment_credential. An expired challenge returns 410 with code payment_expired. Challenges expire after five minutes, or sooner if the invoice expires first. Request a new payment if an unpaid challenge has expired.

If a successful MPP validation response is lost, submitting the same payment ID and credential returns the original receipt. L402 credentials can be validated again while their access restrictions and expiry allow it.

Wait for Nodana to authorise paid access before running the operation. If payment is still required, relay the returned challenge. If access is authorised, return your result and set the Payment-Receipt header when Nodana supplies a receipt.

Use the authorisation result to decide whether to serve the request. A status lookup helps track payment but does not authorise another use.

For request fields, responses, and errors, see Create a payment, Validate a payment, and Get payment status.

On this page