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.
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
idstringrequiredchannel = crypto-inapp.Request bodyobjectsigned_b64stringoptionalsignaturestringoptionalResponse
When the invoice has already left pending (e.g. the watcher settled it first):
{
"signature": "5VErh5...",
"submitted": false,
"already_processed": true,
"status": "paid"
}signaturestringsignature, or, for the signed_b64 path, the base58 signature returned by the network after broadcast.submittedbooleantrue 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_processedbooleantrue when the invoice was no longer pending at submit time; no broadcast occurred.statusstringalready_processed; the current invoice status (e.g. paid, expired, cancelled).Errors
The error body follows the shared envelope { "error": "<message>" } — see
errors for details.
| Code | Meaning | When it happens |
|---|---|---|
400 | signature or signed_b64 required | Neither field was supplied. |
400 | bad json | The request body could not be parsed. |
400 | invoice_not_inapp | The invoice's channel is not crypto-inapp. |
400 | bad signature | The supplied signature is not valid base58. |
401 | auth required | No valid session. |
404 | invoice not found | The invoice does not exist, or does not belong to the session's user. |
502 | submit_failed | The signed transaction was rejected by the network on broadcast. |
503 | inapp not configured | The in-app billing handler is not enabled on this server. |
503 | rpc not configured | The 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.
submitmust follow build in-app transaction: build returns an unsigned transaction with a blockhash and ablockhash_valid_untilTTL (~90s). Sign and submit before the blockhash expires, or rebuild. - Two paths, pick one. Send
signed_b64when you want the backend to broadcast; sendsignaturewhen the wallet already broadcast. There is no benefit to sending both. - Settlement is asynchronous. A
submitted: trueresponse means the transaction was accepted for broadcast, not that the invoice is paid. Watch the invoice's SSE event stream for theinvoice_paidevent, or poll get invoice, to confirm settlement. - Idempotent. Re-submitting after the invoice settles is safe and returns
200withalready_processed: true.