Skip to main content
The fulfiller flow is used when you want to pay — as a customer at a merchant or a friend settling a bill.

Overview

  1. Activate payment mode (biometric gate, integrator-triggered)
  2. Listen for a SonicPay audio signal
  3. Review the payment details
  4. Sign the transaction locally and submit via Cherp

Step 1: Listen for audio

Once the user activates payment mode, open the microphone and listen for SonicPay audio:
const received = await sdk.listen();
// received.intentId: string
The SDK decodes the near-ultrasonic audio signal and extracts the intent ID.

Step 2: Handle the received intent

You can use the default UI or build a custom one:

Default UI

The SDK renders a native bottom sheet with payment details and a confirm button:
sdk.onIntentReceived(received.intentId, wallet);
// Handles everything — fetch, display, sign, submit

Headless mode

Render your own UI and call sdk.confirm() when the user is ready:
sdk.onIntentReceived(received.intentId, wallet, {
  headless: true,
  onDetails: (intent, summary) => {
    // summary: { amount, token, requestor, ataRentLamports,
    //            estimatedFee, expiresAt }
    // Render your own UI
  },
  onComplete: (intent) => {
    // Payment confirmed
  },
  onError: (error) => {
    // Structured error
  },
});

// When the user taps confirm:
await sdk.confirm();

What happens internally

When the fulfiller confirms:
  1. POST /v1/intents/:id/detected — signals the requestor SDK that a payer was found
  2. GET /v1/intents/:id — fetches the full intent details
  3. POST /v1/intents/:id/build — gets the unsigned transaction blob (includes ATA check on Solana)
  4. wallet.signTransaction(blob) — the wallet signs locally, private key never leaves the device
  5. POST /v1/intents/:id/submit — Cherp broadcasts to chain
  6. SDK listens via SSE for confirmed state
Confirmation typically takes under 5 seconds from submission.

Retry handling

On Solana, transactions can be dropped if they expire (~60s). When this happens:
  • The intent resets to created with retryRequired: true
  • The SDK surfaces a re-sign prompt to the fulfiller
  • A new transaction is built with a fresh blockhash