> ## Documentation Index
> Fetch the complete documentation index at: https://docs.overflow.co/llms.txt
> Use this file to discover all available pages before exploring further.

# onSubmit

> Fires when an element produces a submittable value.

`onSubmit` fires when an element produces a submittable value. For inputs, it fires when you call `element.submit()` after validation passes. For wallet elements, it fires when the wallet sheet resolves with a cryptogram. `value` is non-null by construction.

## Payload

```typescript theme={null}
interface OverflowSubmitEvent<T> {
  elementType: T;
  value: ElementValueMap[T]; // non-null
}
```

## Example

```javascript theme={null}
card.on('onSubmit', async ({ value }) => {
  await fetch('/your-backend/authorize', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ paymentMethod: value }),
  });
});
```

## Merchant-driven vs. self-driven

* `card`, `bank`, `email`, `fullName`, `phone`, `companyName`, address elements, and custom elements are merchant-driven: you call `element.submit()` (from a button click, a form submit, or an external CTA).
* `applePay` and `googlePay` are self-driven: the wallet sheet triggers `onSubmit` directly when the shopper authorizes.
* `checkout` composes both: card and bank submit through the internal button, wallets submit through their sheet.
* `submitButton` is a relay: it fires `onSubmit` with `value: undefined` on every click. Use it to drive an external `card.submit()` or `bank.submit()` call.

## Never call the payment gateway directly

For card and wallets, `value` is a tokenized payload. Forward it to your server, which calls the Overflow authorize endpoint. Calling the payment gateway directly from the browser bypasses risk evaluation and the SDK's payment-settings layer.

## Do not return a promise expecting the SDK to await it

The contract is fire-and-forget. If you need request-in-flight UI, manage it in your own state:

```javascript theme={null}
let inFlight = false;

card.on('onSubmit', async ({ value }) => {
  if (inFlight) return;
  inFlight = true;
  spinner.hidden = false;
  try {
    await postToBackend(value);
  } finally {
    inFlight = false;
    spinner.hidden = true;
  }
});
```

## Checkout

Switch on `value.method` first; the per-method key is then non-null:

```javascript theme={null}
checkout.on('onSubmit', async ({ value }) => {
  const { method, contact, billingAddress } = value;
  const paymentMethod =
    method === 'card'      ? { type: 'scheme', ...value.card } :
    method === 'bank'      ? bankPayload(value.bank) :
    method === 'applePay'  ? { type: 'applepay',  walletToken: value.applePay.walletToken } :
                             { type: 'googlepay', walletToken: value.googlePay.walletToken };

  await fetch('/your-backend/authorize', {
    method: 'POST',
    body: JSON.stringify({ paymentMethod, contact, billingAddress }),
  });
});
```

## Wallets

The `onSubmit.value` for wallets is:

```typescript theme={null}
interface WalletElementValue {
  walletToken: string;
  walletType: 'applePay' | 'googlePay';
  email?: string;
  name?: { firstName: string; lastName: string };
  phone?: string;
  billingAddress?: AddressValue;
  shippingAddress?: AddressValue;
}
```

Contact and address keys appear only when the wallet sheet returned them. Set `require.email`, `require.name`, and so on in the wallet options to ask the sheet for them. Keys the sheet did not return are omitted, never `null`.

## See also

* [`onChange`](/payment-elements/events/on-change)
* [`onError`](/payment-elements/events/on-error)
* [Wallets guide](/payment-elements/guides/wallets)
* [Accept a payment](/payment-elements/guides/accept-a-payment)
