TriportRPC

Submit in-app transaction

Notify the billing backend that a signed Solana payment transaction for an in-app invoice has been (or should be) broadcast.

Solana

This endpoint is the second half of the in-app payment flow. After you call build in-app transaction and have the user's wallet sign the returned unsigned transaction, you call submit to tell the backend the payment is on its way.

It accepts one of two bodies:

  • signed_b64 — the full signed, serialised transaction (base64). The backend relays it to the Solana network on your behalf and returns the resulting signature.
  • signature — a base58 transaction signature, used when the wallet already broadcast the transaction itself (e.g. signAndSendTransaction). The backend simply acknowledges it.

submit does not mark the invoice paid. A background watcher detects the transaction on-chain and transitions the invoice to paid independently — so submit is a best-effort UX signal plus an optional relay path. This makes the call idempotent against the watcher: if the invoice is already settled by the time you submit, you get a 200 with already_processed: true.

This endpoint is for the in-app channel only (channel = crypto-inapp) on the Solana rails (sol-native, sol-spl-usdc, sol-spl-usdt). It is unavailable (503) when the in-app handler is not configured on the server.

Parameters

Path parameters

idstringrequired
The invoice ID. Must belong to the authenticated session's user and have channel = crypto-inapp.
Request bodyobject
signed_b64stringoptional
The full signed transaction, base64-encoded. The backend deserialises it and broadcasts it to the network, then returns the on-chain signature.
signaturestringoptional
A base58 transaction signature for a transaction the wallet already broadcast. Validated for syntax only; not re-broadcast.

Response

When the invoice has already left pending (e.g. the watcher settled it first):

{
  "signature": "5VErh5...",
  "submitted": false,
  "already_processed": true,
  "status": "paid"
}
signaturestring
The transaction signature. Echoed back from signature, or, for the signed_b64 path, the base58 signature returned by the network after broadcast.
submittedboolean
true when the backend broadcast the transaction itself (signed_b64 path). false when it only acknowledged a wallet-broadcast signature, or when the invoice was already processed.
already_processedboolean
Present and true when the invoice was no longer pending at submit time; no broadcast occurred.
statusstring
Present alongside already_processed; the current invoice status (e.g. paid, expired, cancelled).

Errors

The error body follows the shared envelope { "error": "<message>" } — see errors for details.

CodeMeaningWhen it happens
400signature or signed_b64 requiredNeither field was supplied.
400bad jsonThe request body could not be parsed.
400invoice_not_inappThe invoice's channel is not crypto-inapp.
400bad signatureThe supplied signature is not valid base58.
401auth requiredNo valid session.
404invoice not foundThe invoice does not exist, or does not belong to the session's user.
502submit_failedThe signed transaction was rejected by the network on broadcast.
503inapp not configuredThe in-app billing handler is not enabled on this server.
503rpc not configuredThe signed_b64 relay path is unavailable because no RPC client is wired.

Note that an already-settled invoice is not an error — it returns 200 with already_processed: true.

Examples

JavaScript (fetch)

async function submitInappTx(invoiceId, body) {
  const res = await fetch(
    `https://api.triport.io/v1/billing/invoices/${invoiceId}/inapp/submit`,
    {
      method: "POST",
      credentials: "include", // send the console session cookie
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body), // { signed_b64 } or { signature }
    },
  );
  if (!res.ok) throw new Error((await res.json()).error);
  return res.json();
}


// After the wallet signs the tx returned by /inapp/build:
const result = await submitInappTx("inv_8f2c1a9d", { signed_b64: signedTxB64 });
console.log(result.signature, result.submitted);

TypeScript SDK (@triport/sdk)

import { TriportClient } from "@triport/sdk";


const client = new TriportClient(); // uses the console session cookie


const result = await client.billing.submitInappTx("inv_8f2c1a9d", {
  signed_b64: signedTxB64,
});


if (result.already_processed) {
  console.log(`invoice already ${result.status}`);
} else {
  console.log(`submitted: ${result.signature}`);
}

Python (triport-sdk)

from triport import TriportClient


client = TriportClient()  # uses the console session cookie


result = client.billing.submit_inapp_tx(
    "inv_8f2c1a9d",
    signed_b64=signed_tx_b64,
)


if result.get("already_processed"):
    print("invoice already", result["status"])
else:
    print("submitted", result["signature"])

Notes

  • Call order matters. submit must follow build in-app transaction: build returns an unsigned transaction with a blockhash and a blockhash_valid_until TTL (~90s). Sign and submit before the blockhash expires, or rebuild.
  • Two paths, pick one. Send signed_b64 when you want the backend to broadcast; send signature when the wallet already broadcast. There is no benefit to sending both.
  • Settlement is asynchronous. A submitted: true response means the transaction was accepted for broadcast, not that the invoice is paid. Watch the invoice's SSE event stream for the invoice_paid event, or poll get invoice, to confirm settlement.
  • Idempotent. Re-submitting after the invoice settles is safe and returns 200 with already_processed: true.