# Withdrawal

Send money from your Payout balance to a bank account. Withdrawals use the [M2M Withdrawals](https://developers.payout.tech/guides/m2m.html) API (API v2): a separate mTLS host, your QWAC certificate for the connection and a QSEAL signature on every request that creates or cancels a withdrawal.

## Before you begin

- Approved QWAC and QSEAL certificates. Get them from a supported Qualified Trust Service Provider (currently I.CA and Disig), import both with `POST /api/v1/mtls/certificates` and wait until Payout approves them. [Certificates](https://developers.payout.tech/guides/certificates.html#setup) describes the whole process.
- An API key, as in [Simple payment](https://developers.payout.tech/guides/payment-gateway-use-cases-simple-payment.html#step-1), step 1.
- A Bearer token, as in [Simple payment](https://developers.payout.tech/guides/payment-gateway-use-cases-simple-payment.html#step-2), step 2. Get it from the standard host (`https://sandbox.payout.one`), not from the mTLS host.

## Steps

1. **Prepare and sign the request**

   Prepare the body and sign it with your QSEAL certificate:

   ```bash
   BODY='{
       "amount": 300,
       "currency": "EUR",
       "external_id": "PAYOUT-2026-0001",
       "iban": "SK3112000000198742637541",
       "customer": {
           "first_name": "John",
           "last_name": "Doe",
           "email": "john.doe@example.com"
       },
       "statement_descriptor": "Simple statement description"
   }'
   DIGEST="SHA-256=$(printf %s "$BODY" | openssl dgst -sha256 -binary | base64)"
   JWS_SIGNATURE="<detached JWS over $DIGEST, made with your QSEAL key>"
   ```

   `amount` is in cents: 300 is 3.00 EUR. The same applies to other currencies, so 30000 is 300 CZK.

   `Digest` is the SHA-256 of the exact body you send. `X-JWS-Signature` is a detached JWS over the `Digest` value, made with the private key of your QSEAL certificate. Its protected header carries the thumbprint of the QSEAL certificate (`x5t#S256`) and the signing time (`sigT`), which must be within 5 minutes of the server time. [M2M Withdrawals](https://developers.payout.tech/guides/m2m.html#signing-payment-instructions-with-qseal) shows the full header and a signing example.

   > [!NOTE]
   > API v1 required a `signature` and a `nonce` in the body. With QSEAL they are no longer needed: the QSEAL signature covers the whole body.

2. **Create the withdrawal**

   Send the request to the mTLS host. The connection presents your QWAC; the request carries the Bearer token and the QSEAL headers from step 1.

   ```bash
   curl --location --request POST 'https://api-mtls-sandbox.payout.one/api/v2/withdrawals' \
   --cert qwac.pem --key qwac.key \
   --header 'Content-Type: application/json' \
   --header 'Accept: application/json' \
   --header 'Authorization: Bearer SFMyNTY.EXAMPLE-TOKEN.dGhpcy1pcy1hLWZha2Utc2lnbmF0dXJlLWV4YW1wbGU' \
   --header 'Idempotency-Key: 9df1a34b-0c34-4565-bde8-50dd17b336f3' \
   --header "Digest: $DIGEST" \
   --header "X-JWS-Signature: $JWS_SIGNATURE" \
   --data "$BODY"
   ```

   A successful call returns `201 Created` with the withdrawal:

   ```json
   {
       "id": 52331,
       "object": "withdrawal",
       "amount": 300,
       "api_key_id": 42,
       "currency": "EUR",
       "external_id": "PAYOUT-2026-0001",
       "iban": "SK3112000000198742637541",
       "idempotency_key": "9df1a34b-0c34-4565-bde8-50dd17b336f3",
       "status": "pending",
       "metadata": {},
       "statement_descriptor": "Simple statement description",
       "created_at": 1759744800,
       "nonce": "ZUc0Mk9sVXZDOXNsdklzMQ",
       "customer": {
           "first_name": "John",
           "last_name": "Doe",
           "name": "John Doe",
           "email": "john.doe@example.com",
           "phone": null,
           "note": null
       },
       "signature": "54476b2fc64161ed45036ec6a35a8f8d6b07e13f4d93b24c6247cf6dbbacd68c"
   }
   ```

   Sending the same `Idempotency-Key` again returns the existing withdrawal instead of creating a new one.

   Responses in API v2 are still signed as in API v1. To check that a response comes from Payout, compute the signature from the response values and compare it with `signature`. `external_id` is an empty string when it is `null`.

   ```text
   Pattern: amount|currency|external_id|iban|nonce|client_secret
   Input:   300|EUR|PAYOUT-2026-0001|SK3112000000198742637541|ZUc0Mk9sVXZDOXNsdklzMQ|q3dpHpYtDrH-KmGD4HMn5OTEx6IsZPBokQ8CqMONWqMSEePWy9bXd3Ua3KvO7f6C
   SHA-256: 54476b2fc64161ed45036ec6a35a8f8d6b07e13f4d93b24c6247cf6dbbacd68c
   ```

   > [!NOTE]
   > The hash is in lowercase hex. Some libraries return uppercase hex; convert it to lowercase before you compare it.

3. **Check the status**

   Reading a withdrawal needs the QWAC and the Bearer token, but no QSEAL signature.

   ```bash
   curl --location --request GET 'https://api-mtls-sandbox.payout.one/api/v2/withdrawals/52331' \
   --cert qwac.pem --key qwac.key \
   --header 'Accept: application/json' \
   --header 'Authorization: Bearer SFMyNTY.EXAMPLE-TOKEN.dGhpcy1pcy1hLWZha2Utc2lnbmF0dXJlLWV4YW1wbGU'
   ```

   | Status | Meaning |
   | --- | --- |
   | `pending` | Created, not processed yet |
   | `in_transit` | Sent to the bank, not confirmed yet |
   | `paid` | Paid out |
   | `canceled` | Cancelled, not paid out |
   | `failed` | Failed, not paid out |

4. **Cancel the withdrawal if needed**

   You can cancel only a withdrawal that is still `pending` and hasn't been passed to the bank yet. [Check if withdrawal can be cancelled](https://developers.payout.tech/api/payment.html#withdrawal_cancel_allowed) tells you in advance whether this is still possible.

   Sign the cancel request with QSEAL as in step 1. It has no body, so `Digest` is the SHA-256 of an empty body.

   ```bash
   DIGEST="SHA-256=$(printf %s "" | openssl dgst -sha256 -binary | base64)"
   JWS_SIGNATURE="<detached JWS over $DIGEST, made with your QSEAL key>"
   curl --location --request POST 'https://api-mtls-sandbox.payout.one/api/v2/withdrawals/52331/cancel' \
   --cert qwac.pem --key qwac.key \
   --header 'Accept: application/json' \
   --header 'Authorization: Bearer SFMyNTY.EXAMPLE-TOKEN.dGhpcy1pcy1hLWZha2Utc2lnbmF0dXJlLWV4YW1wbGU' \
   --header "Digest: $DIGEST" \
   --header "X-JWS-Signature: $JWS_SIGNATURE"
   ```

In production, use `https://app.payout.one` for the Bearer token and `https://api-mtls.payout.one` for the withdrawal calls.

## Next steps

- [Create withdrawal](https://developers.payout.tech/api/payment.html#create_withdrawal) and the other operations in the API reference list all parameters, responses and errors.
- [M2M Withdrawals](https://developers.payout.tech/guides/m2m.html) covers Verification of Payee and the HTTP error codes.
