# Withdrawal

A withdrawal sends money from your Payout balance to a bank account. Withdrawals go through 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.

1. Get and import your certificates
   * Obtain a QWAC and a QSEAL certificate from a supported Qualified Trust Service Provider (currently I.CA and Disig).
   * Import both via `POST /api/v1/mtls/certificates` and wait until Payout approves them. The whole process is described in [Certificates](https://developers.payout.tech/guides/certificates.html#setup).

2. Generate api key and secret
   * You can do it in backoffice ([Sandbox](https://sandbox.payout.one/developers/keys/new) or [Production](https://app.payout.one/developers/keys/new)) after you have already created account.
   * It's required to add notify url before api key is generated. This url will be used for sending webhook notifications (POST HTTP Requests) about state of checkouts and other events.

3. Make authorization call to get Bearer token. This call goes to the standard host, not the mTLS one.

   ```bash
   curl --location --request POST 'https://sandbox.payout.one/api/v1/authorize' \
   --header 'Content-Type: application/json' \
   --header 'Accept: application/json' \
   --data-raw '{
    "client_id": "DC995618-7ED8-4070-9DA0-48B6F86551C3",
    "client_secret": "q3dpHpYtDrH-KmGD4HMn5OTEx6IsZPBokQ8CqMONWqMSEePWy9bXd3Ua3KvO7f6C"
   }'
   ```
   Response of this call looks like this:
   ```json
   {
    "token": "SFMyNTY.g2gDYSFuBgCaSXELfgFiAAFRgWnBcvEfet1jIr9OPF984RGTKu-8HcHPQKJitk_kJKiU",
    "valid_for": 6000
   }
   ```

4. Prepare the withdrawal 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@payout.one"
       },
       "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>"
   ```
   > [!NOTE]
   > Amount is in cents (EUR), so 3 EUR is 300. It's same for other currencies, for example 300 CZK is 30000.

   `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. The full header and a signing example are in [M2M Withdrawals](https://developers.payout.tech/guides/m2m.html#signing-payment-instructions-with-qseal).

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

5. Create the withdrawal on the mTLS host. The connection presents your QWAC, the call carries the Bearer token from step 3 and the QSEAL headers from step 4.
   ```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 'Authorization: Bearer SFMyNTY.g2gDYSFuBgCaSXELfgFiAAFRgA.WnBcvEfet2jJr4OPF984RGTKu-8HcHPQKJitk_kJKiU' \
   --header 'Accept: application/json' \
   --header 'Idempotency-Key: 74775d02-745f-4198-cf3c-be9f1971dabe' \
   --header "Digest: $DIGEST" \
   --header "X-JWS-Signature: $JWS_SIGNATURE" \
   --data "$BODY"
   ```
   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": "74775d02-745f-4198-cf3c-be9f1971dabe",
       "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@payout.one",
           "phone": null
       },
       "signature": "0a6b7c2d9e1f4a3b5c8d7e6f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b"
   }
   ```
   Sending the same `Idempotency-Key` again returns the existing withdrawal instead of creating a new one.

6. Check the status of the withdrawal. Reading 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 'Authorization: Bearer SFMyNTY.g2gDYSFuBgCaSXELfgFiAAFRgA.WnBcvEfet2jJr4OPF984RGTKu-8HcHPQKJitk_kJKiU' \
   --header 'Accept: application/json'
   ```
   Withdrawal statuses:
   * `pending` - created, not processed yet
   * `in_transit` - being processed
   * `paid` - processed
   * `canceled` - cancelled
   * `failed` - failed

7. Cancel the withdrawal if needed. Only a withdrawal that is still `pending` and has not been picked up for processing can be cancelled. Cancel is signed with QSEAL like step 4; the request has no body, so `Digest` is the SHA-256 of an empty body. [Check cancel allowed](https://developers.payout.tech/api/payment.html#withdrawal_cancel_allowed) tells you beforehand whether it is still possible.
   ```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 'Authorization: Bearer SFMyNTY.g2gDYSFuBgCaSXELfgFiAAFRgA.WnBcvEfet2jJr4OPF984RGTKu-8HcHPQKJitk_kJKiU' \
   --header 'Accept: application/json' \
   --header "Digest: $DIGEST" \
   --header "X-JWS-Signature: $JWS_SIGNATURE"
   ```

All parameters, responses and errors of these calls are in the [API reference](https://developers.payout.tech/api/payment.html#create_withdrawal). For production use `https://app.payout.one` for the authorization call and `https://api-mtls.payout.one` for the withdrawal calls.
