Skip to main content

Submit a Payment

Step-by-step guide for submitting a signed payment payload to the wallet-gateway and handling the asynchronous status updates.

Prerequisites: read SSF-SPEC-001 guides — Submit a Payment to understand how to construct the TransferRequest payload. This guide covers only the gateway interaction.


What You're Doing

After constructing and signing a TransferRequest (per SSF-SPEC-001), you submit it to the gateway via the SUBMIT_PAYMENT message type. The gateway acknowledges receipt immediately and then delivers status updates as the operation progresses.


Step 1 — Fetch the Nonce

Before constructing the payment payload, retrieve the current permit nonce for your wallet:

const noncePayload = { requestId: crypto.randomUUID(), domainSeparator: TOKEN_DOMAIN_SEPARATOR };
const nonceMsg = await buildSignedMessage(signer, 'GET_NONCE', noncePayload);
ws.send(JSON.stringify(nonceMsg));

// In your message handler:
// case 'NONCE_RESULT': nonce = msg.payload.nonce

Step 2 — Fetch the Fee Breakdown

const feesPayload = {
requestId: crypto.randomUUID(),
domainSeparator: TOKEN_DOMAIN_SEPARATOR,
amount: principalAmount.toString(),
acquirerId: acquirerId ?? '0x00000000000000000000000000000000', // Zero-UUID if no acquirer
};
const feesMsg = await buildSignedMessage(signer, 'GET_FEES', feesPayload);
ws.send(JSON.stringify(feesMsg));

// In your message handler:
// case 'FEES_RESULT': { fees, remaining } = msg.payload.brokenDownAmount
// permitValue = BigInt(remaining) + BigInt(fees)

Step 3 — Construct and Sign the TransferRequest

Construct your TransferRequest as defined in SSF-SPEC-001, Section 8.2, using the nonce and permit value from steps 1 and 2. This involves signing both the Permit and the Binding Signature against their respective EIP-712 domains.

Refer to SSF-SPEC-001 guides — Submit a Payment for the full signing procedure.


Step 4 — Submit to the Gateway

const payloadId = crypto.randomUUID();

const transferRequest = {
payWithPermitParams: { /* ... */ },
payWithPermitSig: { /* ... */ },
permitSig: { /* ... */ },
payloadId,
};

const submitPayload = {
requestId: crypto.randomUUID(),
transferRequest,
};

const submitMsg = await buildSignedMessage(signer, 'SUBMIT_PAYMENT', submitPayload);
ws.send(JSON.stringify(submitMsg));

Step 5 — Handle the Acknowledgement

The gateway responds immediately with SUBMIT_PAYMENT_ACK:

case 'SUBMIT_PAYMENT_ACK':
console.log(`Accepted. Tracking payloadId: ${msg.payload.payloadId}`);
// Status is "ENQUEUING" — processing has begun
break;

Step 6 — Handle Status Updates

Status updates arrive as SUBMISSION_STATUS push messages. Handle each transition:

case 'SUBMISSION_STATUS':
const { payloadId, status, failureReason, failureCategory, txHash } = msg.payload;

switch (status) {
case 'ENQUEUING':
updateUI('Preparing submission...');
break;
case 'PENDING':
updateUI('Validating...');
break;
case 'BROADCASTING':
updateUI(`Submitting to network... tx: ${txHash}`);
break;
case 'SUCCESS':
// Network accepted the transaction — but this is NOT final settlement.
// Await a TRANSFER_NOTIFICATION for confirmed settlement.
updateUI('Processing — awaiting confirmation...');
break;
case 'FAILURE':
updateUI(`Failed [${failureCategory}]: ${failureReason}`);
break;
}
break;

Step 7 — Await Final Settlement Confirmation

SUCCESS means the transaction was submitted without error. It does not mean the payment is complete. Final settlement is confirmed when the transfer-history service observes the on-chain event after sufficient block confirmations. This arrives as a TRANSFER_NOTIFICATION:

case 'TRANSFER_NOTIFICATION':
const { transfer } = msg.payload;
if (transfer.direction === 'OUT' && transfer.value === expectedAmount) {
updateUI('Payment confirmed.');
}
break;

To receive this notification, you must have an active SUBSCRIBE_TRANSFERS subscription for the relevant token before the payment is confirmed. See Subscribe to Updates.


Common Mistakes

MistakeResultFix
Treating SUCCESS as final settlementPremature confirmation shown to userWait for TRANSFER_NOTIFICATION
Using a stale nonce from local stateSignature invalid at submissionAlways fetch nonce via GET_NONCE immediately before constructing the payload
No active transfer subscriptionFinal confirmation never receivedSubscribe to the token's transfer channel before submitting
Reusing a payloadIdALREADY_SUBMITTED errorGenerate a new UUID per submission