A payment transaction using Web Payments starts with the discovery of your
payment app. Learn how to set up a payment method and get your payment app
ready for merchants and customers to make payments.


![Eiji Kitamura](https://web.dev/images/authors/agektmr.jpg) Eiji Kitamura [X](https://twitter.com/agektmr) [GitHub](https://github.com/agektmr) [Mastodon](https://infosec.exchange/@agektmr) [Bluesky](https://bsky.app/profile/agektmr.com)


Published: September 27, 2017, Last updated: October 16, 2026

<br />

To be used with the Payment Request API, a payment app must be associated with a
payment method identifier. Merchants that want to integrate with a payment app
will use the payment method identifier to indicate that to the browser. This
article discusses how payment app discovery works, and how to configure your
payment app to be properly discovered and invoked by a browser.

If you are new to Web Payments, read the
[Web Payments overview](https://web.dev/articles/web-payments-overview.md.txt) first.

## Payment app types

A payment method can be supported by an Android payment app, a web-based
payment app, or both:

- **Android payment app:** An installed app that Chrome launches with an Android intent. Chrome verifies the app's package name and signing certificate.
- **Web-based payment app:** A service worker that Chrome launches in a modal window. Users don't need to install anything.

Android payment apps are available in Chrome on Android and in WebView.
Web-based payment apps are available in Chrome on all platforms, but not in
WebView.

If both app types are available for a payment method and your web app manifest
sets `prefer_related_applications` to `true`, Chrome hides your web-based
payment app. Otherwise, users might see both in the Payment Request UI and
choose between them.

## Browser support

Web Payments consists of a few different pieces of technologies and the support
status depends on the browser.

|---|---|---|---|---|---|
|   | Chromium |   | Safari |   | Firefox |
|   | Desktop | Android | Desktop | Mobile | Desktop/Mobile |
| Payment Request API | ✔ | ✔ | ✔ | ✔ |   |
| Web-based Payment Handler API | ✔ | ✔ |   |   |   |
| iOS/Android payment app | ✔ | ✔ | ✔\* | ✔\* |   |

> [!NOTE]
> **Note:** \*Safari supports Apple Pay but no third party payment apps.

## How a browser discovers a payment app

Every payment app needs to provide the following:

- URL-based payment method identifier
- Payment method manifest (except when the payment method identifier is provided by a third party)
- Web app manifest

![Diagram: How a browser discovers the payment app from a URL-based payment method identifier](https://web.dev/static/articles/setting-up-a-payment-method/image/diagram-a-browser-disco-ee5623b0facb6.png)

The discovery process starts when a merchant initiates a transaction:

1. The browser sends a request to the [payment method
   identifier](https://w3c.github.io/payment-method-manifest/) URL and fetches the [payment method
   manifest](https://w3c.github.io/payment-method-manifest/).
2. The browser determines the [web app
   manifest](https://developer.mozilla.org/docs/Web/Manifest) URL from the payment method manifest and fetches the web app manifest.
3. The browser determines whether to launch the OS payment app or the web-based payment app from the web app manifest.

The next sections explain in detail how to set up your own payment method so
that browsers can discover it.

## Step 1: Provide the payment method identifier

A [payment method
identifier](https://w3c.github.io/payment-method-id/#dfn-payment-method-identifiers)
is a URL-based string. For example, Google Pay's identifier is
`https://google.com/pay`. Payment app developers can pick any URL as a payment
method identifier as long as they have control over it and can serve arbitrary
content and HTTP headers. In this article, we'll use
[`https://bobbucks.dev/pay`](https://bobbucks.dev/pay) as the payment method
identifier.

> [!NOTE]
> **Note:** Payment apps can support third party payment methods as well.

### How merchants use the payment method identifier

A `PaymentRequest` object is constructed with a list of [payment method
identifiers](https://www.w3.org/TR/payment-method-id/) that identifies payment
apps a merchant decides to accept. Payment method identifiers are set as a value
for the `supportedMethods` property. For example:

\[merchant\] requests payment:

```javascript
const request = new PaymentRequest([{
  supportedMethods: 'https://bobbucks.dev/pay'
}], {
  total: {
    label: 'total',
    amount: { value: '10', currency: 'USD' }
  }
});
```

## Step 2: Serve the payment method manifest

A [payment method manifest](https://w3c.github.io/payment-method-manifest/) is a
JSON file that defines which payment app can use this payment method.

### Provide the payment method manifest

When a merchant initiates a payment transaction, [the browser sends an HTTP
`HEAD` request to the payment method identifier
URL](https://w3c.github.io/payment-method-manifest/#accessing). The payment
method identifier URL responds with a `Link` HTTP header that points to the URL
where the browser can fetch the payment method manifest.

> [!NOTE]
> **Note:** Redirects are allowed up to three times within the [same-site](https://web.dev/articles/same-site-same-origin.md.txt) for a payment method identifier in Chrome.

Configure the payment method server to respond with a HTTP `Link` header with
the `rel="payment-method-manifest"` attribute and the [payment method
manifest](https://w3c.github.io/payment-method-manifest/) URL. For example, if
the manifest is at `https://bobbucks.dev/pay/payment-manifest.json`, the
response header would include:

```console
Link: <https://bobbucks.dev/pay/payment-manifest.json>; rel="payment-method-manifest"
```

The URL can be a fully-qualified domain name or a relative path. Inspect
`https://bobbucks.dev/pay` for network traffic to see an example. You may use a
`curl` command as well:

```bash
curl --include https://bobbucks.dev/pay
```

> [!NOTE]
> **Note:** Learn more about [payment method practices at W3C documentation](https://github.com/w3c/payment-request-info/wiki/PaymentMethodPractice).

The browser sends an HTTP `GET` request to the payment method manifest
URL next. The server responds with the payment method manifest body.

> [!NOTE]
> **Note:** The request isn't necessarily sent to the payment method identifier depending on the combination of the identifier and the browser. For example, Chrome doesn't send a request for `https://play.google.com/billing` and - in some instances - `https://google.com/pay`.

A payment method manifest has two fields, `default_applications` and
`supported_origins`.

| Property name | Description |
|---|---|
| `default_applications` (required) | An array of URLs that points to web app manifests where the payment apps are hosted. (The URL can be a relative). This array is expected to reference the development manifest, production manifest, etc. |
| `supported_origins` | An array of URLs that points to origins that may host third-party payment apps implementing the same payment method. Note that a payment method can be implemented by multiple payment apps. |
[Payment method manifest fields]

A payment method manifest file should look like this:

\[payment handler\] /payment-manifest.json:

```json
{
  "default_applications": ["https://bobbucks.dev/manifest.json"],
  "supported_origins": [
    "https://alicepay.friendsofalice.example"
  ]
}
```

When the browser reads the `default_applications` field, it finds a list of
links to [web app manifests](https://w3c.github.io/manifest/) of supported
payment apps.

> [!NOTE]
> **Note:** Chrome supports a single default payment app per single payment method.

## Step 3: Serve a web app manifest

A [web app manifest](https://developer.mozilla.org/docs/Web/Manifest) is
used to define a web app as the name suggests. It's a widely used manifest file
to [define a Progressive Web App (PWA)](https://web.dev/articles/add-manifest.md.txt).

Typical web app manifest would look like this:

\[payment handler\] /manifest.json:

```json
{
  "name": "Pay with Bobpay",
  "short_name": "Bobpay",
  "description": "This is an example of the Payment Handler API.",
  "icons": [
    {
      "src": "images/manifest/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "images/manifest/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "serviceworker": {
    "src": "service-worker.js",
    "scope": "/",
    "use_cache": false
  },
  "payment": {
    "supported_delegations": ["shippingAddress", "payerName", "payerEmail", "payerPhone"]
  },
  "start_url": "/",
  "display": "standalone",
  "theme_color": "#3f51b5",
  "background_color": "#3f51b5",
  "prefer_related_applications": true,
  "related_applications": [
    {
      "platform": "play",
      "id": "com.example.android.samplepay",
      "min_version": "1",
      "fingerprints": [
        {
          "type": "sha256_cert",
          "value": "4C:FC:14:C6:97:DE:66:4E:66:97:50:C0:24:CE:5F:27:00:92:EE:F3:7F:18:B3:DA:77:66:84:CD:9D:E9:D2:CB"
        }
      ]
    }
  ]
}
```

Chrome reads different fields depending on the type of payment app. For an
Android payment app, Chrome reads only `related_applications`. For a web-based
payment app, Chrome reads `name`, `icons`, `serviceworker`, `payment`, and
`prefer_related_applications`.

| Property name | Description |
|---|---|
| `name` (required) | Used as the payment app name. |
| `icons` (required) | Used as the payment app icon. Only Chrome uses these icons; other browsers may use them as fallback icons if you don't specify them as part of the payment instrument. |
| `serviceworker` | Web-based payment apps only. Defines the service worker that runs as the payment app. |
| `serviceworker.src` | The URL to download the service worker script from. |
| `serviceworker.scope` | A string representing a URL that defines a service worker's registration scope. |
| `serviceworker.use_cache` | Whether the browser can use its HTTP cache when fetching the service worker script. |
| `payment.supported_delegations` | Web-based payment apps only. The shipping and contact information the payment app can provide: `shippingAddress`, `payerName`, `payerEmail`, or `payerPhone`. Android payment apps declare this in [`AndroidManifest.xml`](https://web.dev/articles/android-payment-apps-delegation.md.txt#declare_supported_delegations) instead. |
| `related_applications` | Android payment apps only. Identifies the app and its signing certificate. Find more details in the [Android payment apps developer guide](https://web.dev/articles/android-payment-apps-developers-guide.md.txt). |
| `prefer_related_applications` | Set to `true` to hide the web-based payment app when the Android payment app is installed. |
[Important web app manifest fields]

The web app manifest's `name` property is used as the payment app name, `icons`
property is used as the payment app icon.

## How Chrome determines which payment app to launch

### Launching the platform-specific payment app

To launch the platform-specific payment app, the following conditions must be met:

- The `related_applications` field is specified in the web app manifest and:
  - The installed app's package ID and signature match, while the minimum version (`min_version`) in the web app manifest is less than or equal to the version of the installed application.
- The platform-specific payment app is installed and has:
  - An intent filter of `org.chromium.intent.action.PAY`.
  - A payment method identifier specified as the value for the `org.chromium.default_payment_method_name` property.

Check out the [Android payment apps: developer's guide](https://web.dev/articles/android-payment-apps-developers-guide.md.txt)
for more details about how to set these up.

\[payment handler\] /manifest.json

```json
"prefer_related_applications": true,
"related_applications": [{
  "platform": "play",
  "id": "xyz.bobpay.app",
  "min_version": "1",
  "fingerprints": [{
    "type": "sha256_cert",
    "value": "92:5A:39:05:C5:B9:EA:BC:71:48:5F:F2:05:0A:1E:57:5F:23:40:E9:E3:87:14:EC:6D:A2:04:21:E0:FD:3B:D1"
  }]
}]
```

If `prefer_related_applications` is `true` and the platform-specific payment app
is available, Chrome hides the web-based payment app. Otherwise, Chrome also
looks for the web-based payment app.

### Launching the web-based payment app

The web-based payment app should be specified in the web app manifest's `serviceworker` field.

\[payment handler\] /manifest.json:

```json
"serviceworker": {
  "src": "payment-handler.js"
}
```

The browser launches the web-based payment app by sending a `paymentrequest`
event to the service worker. The service worker doesn't have to be registered in
advance. [It can be registered just-in-time](https://web.dev/articles/setting-up-a-payment-method.md.txt#jit-register).

## Understanding the special optimizations

### How browsers can skip the Payment Request UI and launch a payment app directly

In Chrome, when `show()` method of `PaymentRequest` is called, the Payment
Request API displays a browser-provided UI called the "Payment Request UI". This
UI allows users to choose a payment app. After pressing the **Continue** button
in the Payment Request UI, the selected payment app is launched.
Payment Request UI intervenes before launching the payment app.

Showing the Payment Request UI before launching a payment app increases the
number of steps needed for a user to fulfill a payment. To optimize the process,
the browser can delegate fulfillment of that information to payment apps and
launch a payment app directly without showing the Payment Request UI when
`show()` is called.
Skip the Payment Request UI and launch the payment app directly.

To launch a payment app directly, the following conditions must be met:

- `show()` is triggered with a user gesture (for example, a mouse click).
- There is only a single payment app that:
  - Supports the requested payment method identifier.
  - Supports all the shipping and contact information the merchant requests.

> [!NOTE]
> **Note:** Safari currently only supports Apple Pay so it always launches the app directly, skipping the Payment Request UI.

### When is a web-based payment app registered just-in-time (JIT)?

Web-based payment apps can be launched without the user's explicit prior visit
to the payment app website and registering the service worker. The service
worker can be registered just-in-time when the user chooses to pay with the
web-based payment app. There are two variations for the registration timing:

- If the Payment Request UI is shown to the user, the app is registered just-in-time and launched when the user clicks **Continue**.
- If the Payment Request UI is skipped, the payment app is registered just-in-time and launched directly. Skipping the Payment Request UI to launch a just-in-time registered app requires a user gesture, which prevents unexpected registration of cross-origin service workers.

> [!NOTE]
> **Note:** While a single payment method identifier can support multiple payment apps, JIT-registration happens only when the payment method manifest points to a single payment app.

## Build a web-based payment app

When the merchant calls `show()`, Chrome sends a
[`paymentrequest`](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope/paymentrequest_event)
event to your service worker. Call
[`openWindow()`](https://developer.mozilla.org/docs/Web/API/PaymentRequestEvent/openWindow)
inside `respondWith()` to show your payment UI, then resolve with the payment
response.

For the full API, see the
[Web-Based Payment Handler API on MDN](https://developer.mozilla.org/docs/Web/API/Web-Based_Payment_Handler_API).

## Next steps

- [Android payment apps developer guide](https://web.dev/articles/android-payment-apps-developers-guide.md.txt)
- [Provide shipping and contact information from an Android payment app](https://web.dev/articles/android-payment-apps-delegation.md.txt)