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

# Plaid Integration

> Integrate Plaid payments into your app.

This guide covers how to integrate Plaid payments into your app. Before you begin, make sure you've [configured your Plaid connection](/connections/payments/plaid-bank) in the dashboard.

Processing a payment with Plaid involves three steps:

1. **Create a Link token** - Generate a Plaid Link token via the API using your configured settings
2. **Render Plaid Link** - Display the Plaid Link interface to the customer so they can securely connect their bank account
3. **Create a transaction** - Submit the public token received from Plaid Link to process the payment

<Note>
  Looking for a complete working example? Check out this [sample standalone
  Plaid app](https://github.com/gr4vy/sample-standalone-plaid) on GitHub
  that demonstrates the full integration flow.
</Note>

## Step 1: Create a Plaid Link token

Use the [payment service session API](/reference/payment-service-sessions/create-payment-service-session) with the `create-link-token` action to generate a Plaid Link token. This endpoint applies your dashboard settings (identity, signal, balance checks, etc.) to the token request. You can also override any settings by passing custom Plaid Link parameters such as `products`, `additional_consented_products`, and `optional_products` to customize the experience for specific transactions.

Refer to the [Plaid Link token API documentation](https://plaid.com/docs/api/link/#token-create) for a complete list of available parameters.

<CodeGroup>
  ```csharp C# theme={"system"}
  using Gr4vy;
  using Gr4vy.Models.Components;
  using System.Collections.Generic;

  var sdk = new Gr4vySDK(
      id: "example",
      server: SDKConfig.Server.Sandbox,
      bearerAuthSource: Auth.WithToken(privateKey),
      merchantAccountId: "default"
  );

  // SessionAsync takes the ID and the body as separate arguments
  var session = await sdk.PaymentServiceDefinitions.SessionAsync(
      paymentServiceDefinitionId: "plaid-bank",
      sessionRequestBody: new SessionRequestBody()
      {
          Action = "create-link-token",
          Payload = new Dictionary<string, object>
          {
              ["products"] = new[] { "auth" },
              ["optional_products"] = new[] { "signal" }
          }
      }
  );

  Console.WriteLine($"Link token: {session.LinkToken}");
  ```

  ```go Go theme={"system"}
  package main

  import (
    "context"
    "log"

    gr4vy "github.com/gr4vy/gr4vy-go"
    "github.com/gr4vy/gr4vy-go/models/operations"
  )

  func main() {
    ctx := context.Background()

    // Load your private key from disk, env, or your secret manager
    privateKey := "..."

    s := gr4vy.New(
      gr4vy.WithID("example"),
      gr4vy.WithServer(gr4vy.ServerSandbox),
      gr4vy.WithBearerAuth(gr4vy.WithToken(privateKey)),
      gr4vy.WithMerchantAccountID("default"),
    )

    res, err := s.PaymentServiceDefinitions.Session(ctx, operations.SessionRequest{
      Action: "create-link-token",
      PaymentServiceDefinitionID: "plaid-bank",
    }, &operations.WithRequestBody{
      Payload: map[string]interface{}{
        "products":          []string{"auth"},
        "optional_products": []string{"signal"},
      },
    })
    if err != nil {
      log.Fatal(err)
    }
    if res != nil {
      log.Printf("Link token: %s", res.LinkToken)
    }
  }
  ```

  ```java Java theme={"system"}
  import com.gr4vy.sdk.Gr4vy;
  import com.gr4vy.sdk.BearerSecuritySource;
  import com.gr4vy.sdk.Gr4vy.AvailableServers;
  import com.gr4vy.sdk.models.components.SessionRequest;
  import java.util.HashMap;
  import java.util.Map;

  Gr4vy sdk = Gr4vy.builder()
      .id("example")
      .server(AvailableServers.SANDBOX)
      .merchantAccountId("default")
      .securitySource(new BearerSecuritySource.Builder(privateKey).build())
      .build();

  Map<String, Object> payload = new HashMap<>();
  payload.put("products", new String[]{"auth"});
  payload.put("optional_products", new String[]{"signal"});

  var res = sdk.paymentServiceDefinitions().session()
      .action("create-link-token")
      .paymentServiceDefinitionId("plaid-bank")
      .requestBody(payload)
      .call();

  if (res.linkToken().isPresent()) {
      System.out.println("Link token: " + res.linkToken().get());
  }
  ```

  ```js TypeScript theme={"system"}
  import { Gr4vy, withToken } from "@gr4vy/sdk";
  import fs from "fs";

  const gr4vy = new Gr4vy({
    id: "example",
    server: "sandbox",
    bearerAuth: withToken({
      privateKey: fs.readFileSync("private_key.pem", "utf8"),
    }),
  });

  const session = await gr4vy.paymentServiceDefinitions.session(
    {
      action: "create-link-token",
      payload: {
        // Override default products: require auth, make signal optional
        products: ["auth"],
        optional_products: ["signal"],
      },
    },
    "plaid-bank",
  );

  console.log(session.link_token); // Use this in Plaid Link
  ```

  ```php PHP theme={"system"}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Gr4vy;
  use Gr4vy\Auth;

  $sdk = Gr4vy\SDK::builder()
      ->setId('example')
      ->setServer('sandbox')
      ->setSecuritySource(Auth::withToken($privateKey))
      ->setMerchantAccountId('default')
      ->build();

  $response = $sdk->paymentServiceDefinitions->session(
      action: 'create-link-token',
      paymentServiceDefinitionId: 'plaid-bank',
      requestBody: [
          'products' => ['auth'],
          'optional_products' => ['signal']
      ]
  );

  echo "Link token: " . $response->linkToken;
  ```

  ```python Python theme={"system"}
  from gr4vy import Gr4vy, auth

  with Gr4vy(
      id="example",
      server="sandbox",
      merchant_account_id="default",
      bearer_auth=auth.with_token(open("./private_key.pem").read())
  ) as g_client:

      res = g_client.payment_service_definitions.session(
          action="create-link-token",
          payment_service_definition_id="plaid-bank",
          request_body={
              "products": ["auth"],
              "optional_products": ["signal"]
          }
      )

      print(f"Link token: {res.link_token}")
  ```
</CodeGroup>

## Step 2: Render Plaid Link

Display Plaid Link using the `link_token` from the previous step. Plaid Link securely handles bank authentication and account linking for the customer. When the customer successfully links their account, the `onSuccess` callback receives a `publicToken` and metadata containing account information.

For detailed implementation guides, see the [Plaid Link documentation](https://plaid.com/docs/link/).

<CodeGroup>
  ```kotlin Android theme={"system"}
  import com.plaid.link.Plaid
  import com.plaid.link.configuration.LinkTokenConfiguration

  class MainActivity : AppCompatActivity() {

    private val linkAccountToPlaid = registerForActivityResult(
      FastOpenPlaidLink()
    ) { result ->
      when (result) {
        is LinkSuccess -> {
          // Send publicToken to your server to create a transaction
          val publicToken = result.publicToken
          val metadata = result.metadata
          submitPayment(publicToken, metadata)
        }
        is LinkExit -> {
          // Handle user exit
          result.error?.let { error ->
            Log.e("Link", "Link exit with error: ${error.errorMessage}")
          }
        }
      }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)

      // Configure Link with your link_token
      val linkTokenConfiguration = LinkTokenConfiguration.Builder()
        .token(linkToken)
        .build()

      // Create and open Link
      val plaidHandler = Plaid.create(application, linkTokenConfiguration)
      linkAccountToPlaid.launch(plaidHandler)
    }
  }
  ```

  ```swift iOS theme={"system"}
  import LinkKit

  class ViewController: UIViewController {
    private var handler: Handler?

    override func viewDidLoad() {
      super.viewDidLoad()

      // Configure Link with your link_token
      var config = LinkTokenConfiguration(token: linkToken) { success in
        // Send publicToken to your server to create a transaction
        let publicToken = success.publicToken
        let metadata = success.metadata
        self.submitPayment(publicToken: publicToken, metadata: metadata)
      }

      config.onExit = { exit in
        // Handle user exit
        if let error = exit.error {
          print("Link exit with error: \(error)")
        }
      }

      // Create and open Link
      let result = Plaid.create(config)
      switch result {
      case .success(let handler):
        self.handler = handler
        handler.open(presentUsing: .viewController(self))
      case .failure(let error):
        print("Error creating Link: \(error)")
      }
    }
  }
  ```

  ```tsx React theme={"system"}
  import { usePlaidLink } from "react-plaid-link";

  export function BankAccountForm({ linkToken }) {
    const { open, ready } = usePlaidLink({
      token: linkToken,
      onSuccess: (publicToken, metadata) => {
        // Send publicToken to your server to create a transaction
        submitPayment(publicToken, metadata);
      },
      onExit: (error, metadata) => {
        // Handle user exit
        if (error) {
          console.error("Link exit with error:", error);
        }
      },
    });

    return (
      <button onClick={() => open()} disabled={!ready}>
        Link your bank account
      </button>
    );
  }
  ```

  ```html Web theme={"system"}
  <script src="https://cdn.plaid.com/link/v2/stable/link-initialize.js"></script>
  <script>
    const handler = Plaid.create({
      token: linkToken,
      onSuccess: (publicToken, metadata) => {
        // Send publicToken to your server to create a transaction
        submitPayment(publicToken, metadata);
      },
      onExit: (error, metadata) => {
        // Handle user exit
        if (error) {
          console.error("Link exit with error:", error);
        }
      },
    });

    // Open Link when user clicks button
    document.getElementById("link-button").onclick = function () {
      handler.open();
    };
  </script>
  ```
</CodeGroup>

## Step 3: Create a transaction

On your server, use the `publicToken` from Step 2 to [create a transaction](/reference/transactions/new-transaction). The `publicToken` is automatically exchanged for a secure connection to the customer's bank account.

**Key parameters (API field names):**

* `payment_service_id` (`paymentServiceId` in SDKs) - The UUID of your `bank` payment processor connection (for example, Plaid Transfer or Adyen) to process the payment through.
* `payment_method.payment_service_id` (`paymentMethod.paymentServiceId` in SDKs) - Optional. The UUID of the Plaid connection (found in the dashboard under **Settings** -> **Connections** -> **Plaid**). If omitted, the system fails the transaction if multiple `plaid-bank` connectors have been configured.
* `payment_method.account_id` (`accountId` on the payment method in SDKs) - Required if the `identity` product is not enabled on the Link token and/or merchant account. If `identity` is enabled, the account ID is fetched automatically. When provided, specifies which account to charge if the customer linked multiple accounts (available in the Plaid metadata).
* `buyer` - The buyer's first and last name are required for transaction processing and identity matching, unless the `identity` product is enabled and used to fetch this information automatically from Plaid.

<CodeGroup>
  ```csharp C# theme={"system"}
  using Gr4vy;
  using Gr4vy.Models.Components;

  var sdk = new Gr4vySDK(
      id: "example",
      server: SDKConfig.Server.Sandbox,
      bearerAuthSource: Auth.WithToken(privateKey),
      merchantAccountId: "default"
  );

  var res = await sdk.Transactions.CreateAsync(
      transactionCreate: new TransactionCreate()
      {
          Amount = 1299,
          Currency = "USD",
          Country = "US",
          Intent = TransactionCreate.IntentEnum.Capture,
          
          // PaymentMethod is a discriminated union; use the factory method for Plaid
          PaymentMethod = TransactionCreatePaymentMethod.CreatePlaidPaymentMethodCreate(
              new PlaidPaymentMethodCreate()
              {
                  Method = PlaidPaymentMethodCreate.MethodEnum.Plaid,
                  Token = publicToken,
                  AccountId = metadata.Accounts[0].Id
              }
          ),
          
          Buyer = new BuyerCreate()
          {
              BillingAddress = new AddressCreate()
              {
                  FirstName = "John",
                  LastName = "Doe"
              }
          },
          
          PaymentServiceId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      }
  );

  Console.WriteLine($"Status: {res.Transaction?.Status}");
  ```

  ```go Go theme={"system"}
  package main

  import (
  	"context"
  	"log"

  	gr4vy "github.com/gr4vy/gr4vy-go"
  	"github.com/gr4vy/gr4vy-go/models/components"
  )

  func main() {
  	ctx := context.Background()

  	// Load your private key from disk, env, or your secret manager
  	privateKey := "..."

  	s := gr4vy.New(
  		gr4vy.WithID("example"),
  		gr4vy.WithServer(gr4vy.ServerSandbox),
  		gr4vy.WithBearerAuth(gr4vy.WithToken(privateKey)),
  		gr4vy.WithMerchantAccountID("default"),
  	)

  	amount := int64(1299)

  	res, err := s.Transactions.Create(ctx, &components.TransactionCreate{
  		Amount:  &amount,
  		Currency: "USD",
  		Country:  "US",
  		Intent:   components.CreateTransactionIntentCapture,
  		PaymentMethod: &components.PaymentMethodRequest{
  			Method:    components.PaymentMethodRequestMethodPlaid,
  			Token:     publicToken,              // Public token received from Plaid Link
  			AccountID: metadata.Accounts[0].ID, // Optional. Specify which account to charge
  		},
  		Buyer: &components.BuyerCreate{
  			BillingAddress: &components.AddressCreate{
  				FirstName: "John",
  				LastName:  "Doe",
  			},
  		},
  		PaymentServiceID: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", // Your bank payment processor connection ID (e.g., Plaid Transfer)
  	})
  	if err != nil {
  		log.Fatal(err)
  	}
  	if res != nil {
  		log.Printf("Status: %v", res.Transaction.Status)
  	}
  }
  ```

  ```java Java theme={"system"}
  import com.gr4vy.sdk.Gr4vy;
  import com.gr4vy.sdk.BearerSecuritySource;
  import com.gr4vy.sdk.Gr4vy.AvailableServers;
  import com.gr4vy.sdk.models.components.*;
  import java.util.Optional;

  Gr4vy sdk = Gr4vy.builder()
      .id("example")
      .server(AvailableServers.SANDBOX)
      .merchantAccountId("default")
      .securitySource(new BearerSecuritySource.Builder(privateKey).build())
      .build();

  var res = sdk.transactions().create()
      .transactionCreate(TransactionCreate.builder()
          .amount(1299)
          .currency("USD")
          .country("US")
          .intent(TransactionCreate.IntentEnum.CAPTURE)
          .paymentMethod(PaymentMethodRequest.builder()
              .method("plaid")
              .token(publicToken) // Public token received from Plaid Link
              .accountId(metadata.getAccounts().get(0).getId()) // Optional. Specify which account to charge
              .build())
          .buyer(BuyerCreate.builder()
              .billingAddress(AddressCreate.builder()
                  .firstName("John")
                  .lastName("Doe")
                  .build())
              .build())
          .paymentServiceId("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") // UUID of the bank processor connection (e.g. Plaid Transfer) used to process this transaction
          .build())
      .call();

  if (res.transaction().isPresent()) {
      System.out.println("Status: " + res.transaction().get().getStatus());
  }
  ```

  ```js TypeScript theme={"system"}
  import { Gr4vy, withToken } from "@gr4vy/sdk";
  import fs from "fs";

  const gr4vy = new Gr4vy({
    id: "example",
    server: "sandbox",
    merchantAccountId: "default",
    bearerAuth: withToken({
      privateKey: fs.readFileSync("private_key.pem", "utf8"),
    }),
  });

  const response = await gr4vy.transactions.create({
    amount: 1299,
    currency: "USD",
    country: "US",
    intent: "capture",
    paymentMethod: {
      method: "plaid",
      token: publicToken, // Public token received from Plaid Link
      accountId: metadata.accounts[0].id, // Optional. Specify which account to charge
    },
    buyer: {
      billingAddress: {
        firstName: "John",
        lastName: "Doe",
      },
    },
    paymentServiceId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", // Your Plaid connection UUID
  });

  console.log(`Status: ${response.transaction?.status}`);
  ```

  ```php PHP theme={"system"}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Gr4vy;
  use Gr4vy\Auth;

  $sdk = Gr4vy\SDK::builder()
      ->setId('example')
      ->setServer('sandbox')
      ->setSecuritySource(Auth::withToken($privateKey))
      ->setMerchantAccountId('default')
      ->build();

  $response = $sdk->transactions->create(
      transactionCreate: new Gr4vy\TransactionCreate(
          amount: 1299,
          currency: 'USD',
          country: 'US',
          intent: 'capture',
          paymentMethod: new Gr4vy\PaymentMethodRequest(
              method: 'plaid',
              token: $publicToken, // Public token received from Plaid Link
              accountId: $metadata['accounts'][0]['id'] // Optional. Specify which account to charge
          ),
          buyer: new Gr4vy\BuyerCreate(
              billingAddress: new Gr4vy\AddressCreate(
                  firstName: 'John',
                  lastName: 'Doe'
              )
          ),
          paymentServiceId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' // Your Plaid connection UUID
      )
  );

  echo "Status: " . $response->transaction->status;
  ```

  ```python Python theme={"system"}
  from gr4vy import Gr4vy, auth

  with Gr4vy(
      id="example",
      server="sandbox",
      merchant_account_id="default",
      bearer_auth=auth.with_token(open("./private_key.pem").read())
  ) as g_client:

      res = g_client.transactions.create(
          transaction_create={
              "amount": 1299,
              "currency": "USD",
              "country": "US",
              "intent": "capture",
              "payment_method": {
                  "method": "plaid",
                  "token": public_token,  # Public token received from Plaid Link
                  "account_id": metadata["accounts"][0]["id"]  # Optional. Specify which account to charge
              },
              "buyer": {
                  "billing_address": {
                      "first_name": "John",
                      "last_name": "Doe"
                  }
              },
              "payment_service_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"  # Your Plaid connection UUID
          }
      )

      print(f"Status: {res.transaction.status}")
  ```
</CodeGroup>

<Note>
  If Plaid is still fetching identity data and the call times out, the API returns `unavailable_payment_method`. This means Plaid did not finish in time; retry the transaction to allow identity to complete.
</Note>

## Next steps

To enable recurring payments and vault customer bank accounts for future transactions, see [Recurring Payments](/connections/payments/plaid-bank-recurring).

If you're using Plaid Signal, configure your ruleset in [Signal setup](/connections/payments/plaid-bank-signal).
