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

# Generate Bank Link

> US customers only. Returns a hosted page where the customer links the bank account for ACH_PULL payins.

<Prompt description="Generate Bank Link" actions={["cursor"]}>
  Add the Zapyd "Generate Bank Link" call (`POST /cms/api/v1/bank/generate-link`) to my backend. US customers only. Returns a hosted page where the customer links the bank account for ACH\_PULL payins.

  * Sandbox: `POST https://sandbox.zapyd.com/cms/api/v1/bank/generate-link`
  * Production: `POST https://api.zapyd.com/cms/api/v1/bank/generate-link`

  JSON body:

  * `customer_id` (string, uuid, required): Zapyd customer ID (UUID) of a VERIFIED US customer.
  * `redirect_url` (string, required): Where the customer returns after linking, whether it worked or not. Must be a full URL. Example: `https://yourapp.com/bank/return`.
  * `payment_method` (string, optional): The payin method the account is linked for. Defaults to ach\_pull. Case-insensitive. One of: `ach_pull`, `wire`, `debit_card`.

  Success: HTTP 200, `{status: true, message, data}`. `data`: `widget_url` (Hosted bank-linking page. Open it in a browser or webview; it expires, so create a new one for each attempt).
  Errors (`{status: false, message, err_code, errors}`):

  * 400 `This field is required`: `customer_id` or `redirect_url` is missing
  * 400 `Enter a valid URL`: `redirect_url` isn't a full URL
  * 400 `"&lt;value&gt;" is not a valid payment_method. Expected one of: wire, debit_card, ach_pull`: `payment_method` isn't one of the listed values
  * 400 `Customer not found or access denied`: Customer not found, or not in your organization
  * 500 `Internal Server Error`: The customer hasn't completed US KYC, or the link couldn't be created. Check the customer is `VERIFIED` before retrying
    Every endpoint can also return 401 `AUTH_*` (signature, timestamp or key: fix, don't retry), and 429 or 5xx (retry with backoff).

  Rules:

  * US customers only, for `ACH_PULL` payins. The customer must be `VERIFIED`. A customer without completed US KYC gets a 500: don't retry it.
  * Open `widget_url` in a browser or webview. The customer signs in to their bank, picks the account and returns to `redirect_url`, whether it worked or not. Create a new link for each attempt.
  * No webhook is sent. Poll `GET /cms/api/v1/bank/list/{customer_id}` until the account in `accounts` is `VERIFIED`, then quote with `payment_method: ACH_PULL` and that account's `id` as `bank_id` (required). Zapyd debits that account, so the quotation has no `deposit_instructions`.
  * Sandbox: the hosted page isn't available for test customers. Call `POST /cms/api/v1/bank/mock-bank-verification` with the US `customer_id`, `bank_status: VERIFIED` and no `bank_id` to create a linked test account.

  Signing (every request):

  * Headers: `X-API-KEY`, `X-TIMESTAMP` (Unix seconds, within 300 s of server time; generate per request) and `X-SIGNATURE`.
  * `X-SIGNATURE` = Base64(HMAC-SHA256(key = API secret, message = apiKey + "|" + timestamp + "|" + canonicalBody)). Base64 of the raw digest, not hex.
  * canonicalBody: the JSON body with keys sorted at every nesting level, no whitespace (separators `,` and `:`), and every non-ASCII character escaped as lowercase `\uXXXX` (Python `json.dumps(body, sort_keys=True, separators=(",", ":"))`). Requests with no body (GET, DELETE) sign `{}`. Query parameters are not signed.
  * Send the exact canonicalBody string you signed as the request body, with `Content-Type: application/json`.
  * Test vector: key `3f1b2c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d`, secret `test-secret-do-not-use`, timestamp `1735689600`. Signing `{}` gives `6sCtVSRQjU9+2/af8gdwUAvY1l6Ii6ENcbRfanPkhY0=`. Body `{"customer_id":"78c99d71-f28f-47a9-8302-93b286efbe0e","amount":100.5,"currency":"INR","meta":{"note":"Café","b":2,"a":1}}` gives `l+DvQrzlKbsOSxSYOdWoWHEehcFRZfDJPKlLDkm2cSI=`.

  Deliver:

  1. A typed `generateBankLink` function in this codebase's language and HTTP client. Reuse an existing Zapyd client and signer, or write one small shared client.
  2. Config from `ZAPYD_API_KEY`, `ZAPYD_API_SECRET` and `ZAPYD_BASE_URL`. The secret stays on the server, never in a browser or app.
  3. Return `data`. When `status` is false, throw an error with the HTTP status, `err_code`, `message` and `errors`. Don't show raw errors to end users.
  4. Retry only 429 and 5xx: exponential backoff from 1 s, capped at 30 s, at most 5 attempts.
  5. Amounts as strings. Types for every field above.
  6. Tests: the signer against the test vector, and this call against sandbox.

  Reference: `https://docs.zapyd.com/api-reference-exchange/endpoint/bank/generate-link.md`
</Prompt>

## How linking works

1. Your backend calls this endpoint for a `VERIFIED` US customer and gets `widget_url`.
2. Your app opens `widget_url`. The customer signs in to their bank and picks the account.
3. The customer lands on `redirect_url`, whether linking worked or not.
4. Call [Fetch Bank Accounts](/api-reference-exchange/endpoint/bank/list-\{customer_id}). The account is in `accounts`, `PROCESSING` until the connection completes, then `VERIFIED`. No `BANK` webhook is sent, so poll.
5. Create an `ACH_PULL` [payin quotation](/api-reference-exchange/endpoint/payin/quotation/quotation) with that account's `id` as `bank_id`. It's required: without it the quotation fails with `No linked bank account found for the selected payment method`. Zapyd debits that account, so there are no deposit instructions.

<Note>
  In sandbox, the hosted page isn't available for test customers. Call [Mock Bank Verification](/api-reference-exchange/endpoint/bank/mock-bank-verification) with the US `customer_id`, `bank_status: "VERIFIED"` and no `bank_id`: it creates a linked test account that Fetch Bank Accounts returns.
</Note>

## Error Codes and Messages

| API Status Code | Response | Reason |
| - | - | - |
| 400 | This field is required. | `customer_id` or `redirect_url` is missing |
| 400 | Enter a valid URL. | `redirect_url` isn't a full URL |
| 400 | "\<value>" is not a valid payment\_method. Expected one of: wire, debit\_card, ach\_pull. | `payment_method` isn't one of the listed values |
| 400 | Customer not found or access denied. | Customer not found, or not in your organization |
| 500 | Internal Server Error | The customer hasn't completed US KYC, or the link couldn't be created. Check the customer is `VERIFIED` before retrying |


## OpenAPI

````yaml POST /bank/generate-link
openapi: 3.1.0
info:
  title: Zapyd API
  description: API for Zapyd - Customer, Payout, and Webhook services
  license:
    name: MIT
  version: 1.0.0
servers:
  - url: https://sandbox.zapyd.com/pos/api/v1
    description: Payout API Base URL
    variables:
      base_url:
        default: https://sandbox.zapyd.com
  - url: https://sandbox.zapyd.com/cms/api/v1
    description: Customer API Base URL
    variables:
      base_url:
        default: https://sandbox.zapyd.com
security:
  - ApiKeyAuth: []
    TimestampAuth: []
    SignatureAuth: []
tags:
  - name: Customer
    description: Customer related operations
    x-displayName: Customer
    x-traitTag: true
  - name: Payout
    description: Payout related operations
  - name: Webhooks
    description: Webhook related operations
  - name: Widget
    description: Hosted buy/sell widget session initialization
paths:
  /bank/generate-link:
    post:
      tags:
        - Bank
      description: >-
        US customers only. Returns a hosted page where the customer signs in to
        their bank and picks the account for ACH_PULL payins. When they finish,
        they return to redirect_url and the account appears in Fetch Bank
        Accounts. No webhook is sent: poll Fetch Bank Accounts until the account
        is VERIFIED.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - customer_id
                - redirect_url
              properties:
                customer_id:
                  type: string
                  format: uuid
                  description: Zapyd customer ID (UUID) of a VERIFIED US customer.
                  example: c2cf861b-342b-4318-a90e-85cd0312e82f
                redirect_url:
                  type: string
                  format: uri
                  description: >-
                    Where the customer returns after linking, whether it worked
                    or not. Must be a full URL.
                  example: https://yourapp.com/bank/return
                payment_method:
                  type: string
                  enum:
                    - ach_pull
                    - wire
                    - debit_card
                  default: ach_pull
                  description: >-
                    Optional. The payin method the account is linked for.
                    Defaults to ach_pull. Case-insensitive.
                  example: ach_pull
            example:
              customer_id: c2cf861b-342b-4318-a90e-85cd0312e82f
              redirect_url: https://yourapp.com/bank/return
      responses:
        '200':
          description: Link created
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: boolean
                    example: true
                  message:
                    type: string
                    example: Success
                  data:
                    type: object
                    properties:
                      widget_url:
                        type: string
                        format: uri
                        description: >-
                          Hosted bank-linking page. Open it in a browser or
                          webview; it expires, so create a new one for each
                          attempt.
                        example: https://link.example.com/session/7f3c2a9e
              example:
                status: true
                message: Success
                data:
                  widget_url: https://link.example.com/session/7f3c2a9e
        '400':
          description: Bad Request
          content:
            application/json:
              example:
                status: false
                message: Bad Request
                err_code: REQ_FIELD_MISSING
                errors:
                  redirect_url:
                    - This field is required.
      servers:
        - url: https://sandbox.zapyd.com/cms/api/v1
          description: Customer API Base URL
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-KEY
      description: API Key for authentication
    TimestampAuth:
      type: apiKey
      in: header
      name: X-TIMESTAMP
      description: Current timestamp in seconds since epoch
    SignatureAuth:
      type: apiKey
      in: header
      name: X-SIGNATURE
      description: HMAC SHA256 signature of the request encoded in Base64

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.