***

title: 지급대행하기
description: 지급대행은 오픈마켓을 운영하는 사업자 대신 토스페이먼츠가 셀러(입점 판매자)에게 대금 지급을 대행 해주는 서비스예요.
keyword: 오픈마켓, 셀러, 보안 키, 보안키, 지급대행, 잔액 조회
-----------------------------------------

**Version 2**

# 지급대행하기

지급대행은 오픈마켓에서 발생한 매출을 토스페이먼츠가 오픈마켓을 대신하여 셀러(입점 판매자)에게 지급하는 서비스입니다. 직접 수많은 셀러에게 정산할 필요 없이, 정산 업무를 토스페이먼츠한테 맡기고 자금 흐름을 더 효율적으로 관리할 수 있어요.

지급대행 서비스 이용 신청하기

## ENCRYPTION 보안

지급대행은 토스페이먼츠와 직접 계약하지 않은 셀러에게 돈을 지급하는 서비스이기 때문에 KYC 등 강화된 리스크 검토 절차가 있고, 연동 측면에서도 더 강화된 보안을 요구합니다.

Request Body가 있는 지급대행 서비스의 POST 요청은 모두 `ENCRYPTION` 보안을 사용합니다. `ENCRYPTION` 보안은 토스페이먼츠에서 발급하는 보안 키를 사용해서 API의 Request Body를 JWE로 암호화하는 방식입니다. `ENCRYPTION` 보안이 설정된 요청은 응답도 암호화되어 돌아옵니다. 응답도 동일한 보안 키로 복호화해주세요.

`ENCRYPTION` 보안이 적용되는 API는 [셀러 등록](/reference/additional#셀러-등록), [셀러 수정](/reference/additional#셀러-수정), [지급대행 요청](/reference/additional#지급대행-요청)입니다. 지급대행 요청 취소도 POST 요청이지만 Request Body 파라미터가 없기 때문에 `ENCRYPTION` 보안을 사용하지 않습니다.

### 보안 키

토스페이먼츠 개발자센터의 [API 키 메뉴](https://developers.tosspayments.com/my/api-keys)에서 **API 개별 키 > 보안 키**를 확인하세요. 보안 키는 절대 외부에 노출되면 안 됩니다.

![개발자센터 보안 키](https://static.toss.im/photos/ApiKey_7___.png#2448x1380)

보안 키는 64자의 Hexadecimal 문자열입니다. JWE 암호화할 때는 보안 키를 아래와 같이 바이트로 전환해야 됩니다.

Kotlin
Python

```kotlin
fun hexDecode(securityKey: String) String {
  return Hex.decode(securityKey)
}
```

```python
import binascii
def hexDecode(str):
  return binascii.unhexlify(str)
```

### 암호화

아래 코드와 같이 JWE 헤더를 생성한 다음에 Request Body를 암호화해주세요. 필수 헤더 외에 `iat`, `nonce` 커스텀 JWE 헤더를 요구합니다.

* `alg`: 보안 키의 암호화 알고리즘입니다. 토스페이먼츠 보안 키는 암호화되어 있지 않기 때문에 `dir` 값으로 설정해주세요.
* `enc`: JWE 암호화 알고리즘입니다. [`A256GCM`](https://libsodium.gitbook.io/doc/secret-key_cryptography/aead/aes-256-gcm)로 설정해주세요.
* `iat`(issued at): Request Body를 생성한 시간입니다. `yyyy-MM-dd'T'HH:mm:ss±hh:mm` ISO 8601 형식입니다.
* `nonce`: JWE의 고유 식별자입니다. [UUID](/resources/glossary/uuid)와 같이 충분히 무작위적인 고유 값입니다.

Java
Kotlin
Python
PHP

```java title="암호화"
public static String encrypt(Object target, String securityKey) throws Exception {
  // 보안 키를 바이트 배열로 변환
  byte[] key = Hex.decode(securityKey);

  // JWE 헤더 생성
  JWEHeader jweHeader = new JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A256GCM)
    .customParam("iat", OffsetDateTime.now(ZoneId.of("Asia/Seoul")).toString())
    .customParam("nonce", UUID.randomUUID().toString())
    .build();

  // Request Body 암호화
  String payload = objectMapper.writeValueAsString(target);
  JWEObject jweObject = new JWEObject(jweHeader, new Payload(payload));
  jweObject.encrypt(new DirectEncrypter(key));
  return jweObject.serialize();

}

```

```kotlin title="암호화"
fun encrypt(target: Any, securityKey: String): String {
    // 보안 키 바이트로 전환
    val key = Hex.decode(securityKey)

    // JWE 헤더 생성
    val jweHeader = JWEHeader.Builder(JWEAlgorithm.DIR, EncryptionMethod.A256GCM)
        .customParam("iat", OffsetDateTime.now(ZoneId.of("Asia/Seoul")).toString())
        .customParam("nonce", UUID.randomUUID().toString())
        .build()

    // Request Body 암호화
    val payload = objectMapper.writeValueAsString(target)
    val jweObject = JWEObject(jweHeader, Payload(payload))
    jweObject.encrypt(DirectEncrypter(key))
    return jweObject.serialize()
}
```

```python title="암호화"
from authlib.jose import JsonWebEncryption
import uuid
from datetime import datetime
def encrypt(target, securityKey):
  # 보안 키 바이트로 전환
  key = binascii.unhexlify(securityKey)
  # JWE 헤더 생성
  headers = {
    "alg": "dir",
    "enc": "A256GCM",
    "iat": datetime.now().astimezone().isoformat(),
    "nonce": str(uuid.uuid4())
  }
  # Request Body 암호화
  jwe = JsonWebEncryption()
  encrypted = jwe.serialize_compact(headers, target.encode('utf-8'), key)
  return encrypted
```

```php title="암호화"
<?php
  use Jose\Component\Core\JWK;
  use Jose\Component\Core\AlgorithmManager;
  use Jose\Component\Encryption\Algorithm\KeyEncryption\Dir;
  use Jose\Component\Encryption\Algorithm\ContentEncryption\A256GCM;
  use Jose\Component\Encryption\JWEBuilder;
  use Jose\Component\Encryption\Serializer\CompactSerializer;
  use Jose\Component\Encryption\Compression\CompressionMethodManager;
  use Jose\Component\Encryption\Compression\Deflate;
  use Ramsey\Uuid\Uuid;
  use DateTimeImmutable;
  use DateTimeZone;
  use DateTime;

  function encrypt($target, $securityKey){
    // 보안 키 바이트로 전환
    $key = new JWK([
      'kty' => 'oct',
      'k' => base64_encode(hex2bin($securityKey)),
    ]);
    // JWE 빌더 초기화
    $keyEncryptionAlgorithmManager = new AlgorithmManager([
      new Dir(),
    ]);
    $contentEncryptionAlgorithmManager = new AlgorithmManager([
      new A256GCM(),
    ]);
    $compressionMethodManager = new CompressionMethodManager([
      new Deflate(),
    ]);
    $jweBuilder = new JWEBuilder(
      $keyEncryptionAlgorithmManager,
      $contentEncryptionAlgorithmManager,
      $compressionMethodManager
    );

    // Request Body 암호화
    $payload = json_encode($target);
    $issuedAt = (new DateTimeImmutable())->setTimezone(new DateTimeZone('Asia/Seoul'))->format(DateTime::ATOM);
    $nonce = Uuid::uuid4()->toString();
    $jwe = $jweBuilder
      ->create()
      ->withPayload($payload)
      ->withSharedProtectedHeader([ // JWE 헤더
          'alg' => 'dir',
          'enc' => 'A256GCM',
          'iat' => $issuedAt,
          'nonce' => $nonce,
      ])
      ->addRecipient($key)
      ->build();
    // JWE 직렬화(Serlialization)
    $serializer = new CompactSerializer();
    $token = $serializer->serialize($jwe, 0);
    return $token;
  }
?>
```

### 복호화

`ENCRYPTION` 보안이 적용된 요청은 응답도 암호화되어 돌아옵니다. API 응답도 암호화에 사용한 같은 보안 키로 복호화하고 응답 값을 확인하세요. 성공 응답, 실패 응답 모두 암호화되어 돌아옵니다.

Java
Kotlin
Python
PHP

```java title="복호화"
public static String decrypt(String encryptedTarget, String securityKey) throws Exception {
  // 보안 키를 바이트로 변환
  byte[] key = Hex.decode(securityKey);
  // JWE 응답 복호화
  JWEObject jweObject = JWEObject.parse(encryptedTarget);
  jweObject.decrypt(new DirectDecrypter(key));
  if (jweObject.getState() == JWEObject.State.DECRYPTED) {
      return jweObject.getPayload().toString();
  } else {
      throw new JOSEException("Failed to decrypt");
  }
}
```

```kotlin title="복호화"
fun decrypt(encryptedTarget: String, securityKey: String): String {
  // 보안 키 바이트로 전환
  val key = Hex.decode(securityKey)
  // JWE 응답 복호화
  val jweObject = JWEObject.parse(encryptedTarget)
  jweObject.decrypt(DirectDecrypter(key))
  val decryptedJweObject = if (jweObject.state == JWEObject.State.DECRYPTED) {
      jweObject
  } else {
      throw JOSEException("Failed to decrypt")
  }
  return decryptedJweObject.payload.toString()
}
```

```python title="복호화"
from authlib.jose import JsonWebEncryption
def decrypt(encrypted_jwe, hex_key):
  # 보안 키 바이트로 전환
  key = binascii.unhexlify(hex_key)
  # JWE 응답 복호화
  jwe = JsonWebEncryption()
  decrypted = jwe.deserialize_compact(encrypted_jwe, key)
  return decrypted['payload'].decode('utf-8')
```

```php title="복호화"
<?php
  use Jose\Component\Core\JWK;
  use Jose\Component\Core\AlgorithmManager;
  use Jose\Component\Encryption\Algorithm\KeyEncryption\Dir;
  use Jose\Component\Encryption\Algorithm\ContentEncryption\A256GCM;
  use Jose\Component\Encryption\Serializer\CompactSerializer;
  use Jose\Component\Encryption\Compression\CompressionMethodManager;
  use Jose\Component\Encryption\Compression\Deflate;
  use Ramsey\Uuid\Uuid;
  use DateTimeImmutable;
  use DateTimeZone;
  use DateTime;
  use Jose\Component\Encryption\JWEDecrypter;
  use Jose\Component\Encryption\Serializer\JWESerializerManager;
  use Exception;
  function decrypt($target, $securityKey) {
    // 보안 키 바이트로 전환
    $key = new JWK([
      'kty' => 'oct',
      'k'   => base64_encode(hex2bin($securityKey))
    ]);
    // JWE Decrypter 초기화
    $keyEncryptionAlgorithmManager = new AlgorithmManager([
      new Dir(),
    ]);
    $contentEncryptionAlgorithmManager = new AlgorithmManager([
      new A256GCM(),
    ]);

    $compressionMethodManager = new CompressionMethodManager([
      new Deflate(),
    ]);
    $jweDecrypter = new JWEDecrypter(
      $keyEncryptionAlgorithmManager,
      $contentEncryptionAlgorithmManager,
      $compressionMethodManager
    );

    // JWE 역직렬화
    $serializerManager = new JWESerializerManager([
      new CompactSerializer(),
    ]);
    $jwe = $serializerManager->unserialize($target);

    // JWE 응답 복호화
    if (!$jweDecrypter->decryptUsingKey($jwe, $key, 0)) {
      throw new Exception('Failed to decrypt JWE');
    }
    return $jwe->getPayload();
    }

?>

```

***

## 1. 셀러 등록하기

먼저 [셀러 등록 API](/reference/additional#셀러-등록)로 내 오픈마켓에 입점해있는 셀러의 정보를 토스페이먼츠에 등록해주세요. [API 파라미터](/reference/additional#셀러-등록)를 확인해서 Request Body 데이터를 만든 다음에, 해당 데이터를 보안 키로 [암호화](#암호화)해주세요.

```json title="JSON"
{
  "refSellerId": "my-seller-1",
  "businessType": "INDIVIDUAL_BUSINESS",
  "company": {
    "name": "테스트 상호",
    "representativeName": "김토스",
    "businessRegistrationNumber": "1234567890",
    "email": "toss@sample.com",
    "phone": "01012345678"
  },
  "account": {
    "bankCode": "092",
    "accountNumber": "123*****90123",
    "holderName": "김토스"
  },
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
```

```plain title="JWE"
// JWE로 암호화된 Request Body
eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwia \
WF0IjoiMjAyNC0wMS0wMVQwMDowMDowMCswOTowMC \
IsIm5vbmNlIjoiaGVsbG8ifQ..CO1e5UIHhQ7A8Ym \
4.cMFtE6M3VjSRKuBCF0O5oKg0yIW9-wkJqLMJaYz \
77aNgEwwDg2sur3AzD8rFNwb5N44a4b_RbyKqvSeA \
ud_Pl5vmRWuaTs0gGzKTH_CXcvxbYVphqredwQrp1 \
UCmrK602KjQ8cHsT_i3BKZiWA3p-6shRr8jZZ61j7 \
RavkLD2gekCi_PM4jcbbMNTumM8VsgLMnaSQ8cUsO \
NA9CPpPVBCgunU3BjzNduDWt-2a4ZCnktjoPHZtqu \
g6o2TL4O-MdUDVbzbbinQdmd5uATi1V4Ne3Dbmtpc \
RhTyQVfh0vkgeMwY9MdguKlyu6s8hONkNiGPVMNMD \
8INzGYg6sry04o0H9AdXuAcftqFNSqEW**ozwJTm1 \
aonli4YA9V5rnj9jVU4-HRpDy8WI8Rh7NNVhlHakF \
z_vwqFdKhTP51hD6WmMnt96wFtg**PMDLK1DGGm4J \
vuAhvghBNkuBk7G67vJ5ZtPnuIXKSWwRPX-LUrlm5 \
NAiIpaWZmlYTW9mk1-6QXOfFUPkkWxi2Mgvb7e_fx \
_XsFnJ7jJOY-I-BlUJOulDc46.8NsYbB4add3n1rN \
o0qhDQg

```

JWE로 암호화한 Request Body를 API 요청의 데이터값으로 넣고 `TossPayments-api-security-mode: ENCRYPTION` 헤더를 추가하세요. **API 개별 연동 키 > 시크릿 키**를 이용하는 [Basic 인증](/reference/using-api/authorization#인증-헤더)도 반드시 해주세요.

`{SECRET_KEY}` 자리에는 개발자센터의 [API 키 메뉴](https://developers.tosspayments.com/my/api-keys)에서 확인한 시크릿 키를 넣고, `base64()` 부분은 인코딩한 값으로 바꾸세요. 인코딩 방법은 [시크릿 키로 인증하기](/reference/using-api/authorization#시크릿-키로-인증하기)를 참고하세요.

```bash title="cURL"
curl --location 'https://api.tosspayments.com/v2/sellers' \
--header 'Authorization: Basic base64({SECRET_KEY}:)' \
--header 'Content-Type: text/plain' \
--header 'TossPayments-api-security-mode: ENCRYPTION' \
--data 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiaWF0IjoiMjAyNC0wMS0wMVQwMDowMDowMCswOTowMCIsIm5vbmNlIjoiaGVsbG8ifQ..CO1e5UIHhQ7A8Ym4.cMFtE6M3VjSRKuBCF0O5oKg0yIW9-wkJqLMJaYz77aNgEwwDg2sur3AzD8rFNwb5N44a4b_RbyKqvSeAud_Pl5vmRWuaTs0gGzKTH_CXcvxbYVphqredwQrp1UCmrK602KjQ8cHsT_i3BKZiWA3p-6shRr8jZZ61j7RavkLD2gekCi_PM4jcbbMNTumM8VsgLMnaSQ8cUsONA9CPpPVBCgunU3BjzNduDWt-2a4ZCnktjoPHZtqug6o2TL4O-MdUDVbzbbinQdmd5uATi1V4Ne3DbmtpcRhTyQVfh0vkgeMwY9MdguKlyu6s8hONkNiGPVMNMD8INzGYg6sry04o0H9AdXuAcftqFNSqEW**ozwJTm1aonli4YA9V5rnj9jVU4-HRpDy8WI8Rh7NNVhlHakFz_vwqFdKhTP51hD6WmMnt96wFtg**PMDLK1DGGm4JvuAhvghBNkuBk7G67vJ5ZtPnuIXKSWwRPX-LUrlm5NAiIpaWZmlYTW9mk1-6QXOfFUPkkWxi2Mgvb7e_fx_XsFnJ7jJOY-I-BlUJOulDc46.8NsYbB4add3n1rNo0qhDQg'
```

성공적으로 셀러가 등록되면 아래와 같이 JWE로 암호화된 [Seller 객체](/reference/additional#seller-객체)가 돌아옵니다. JWE를 [복호화](#복호화)해서 응답 값을 확인해주세요.

```plain title="JWE"
// JWE로 암호화된 응답
eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwia \
WF0IjoiMjAyNC0wMS0wMVQwMDowMDowMCswOTowMC \
IsIm5vbmNlIjoiaGVsbG8ifQ..IizlGgBcRij4cmh \
D.lSfyK-336F7iSPeYwzg0LR5j90MONILBPTjibg1 \
BolSp3va0yFZiV5xoQB1dVNd3io67QpncH1cTUSs6 \
DTrqth-iX8Yia_R4SmYqm6scZPZrJR7JYnvuxX1YD \
Kv_l6ot9_R7Hb1kDXH_QLKnT0RhLUGjSBEfj1Io6K \
9afgExPMXlhCtxSv5DfmraArxgSI_2ZGuoWnRKjaR \
RymbceKMUyLq6j5wfG9tAGPxdpo3RncHGlAacrBcL \
vV6oBWB3GfVIexRjcMCTy-2REIt_fnJ39d_n0Lw6i \
B7QtErLaW9A3SOkWQkTgMedDzVoQq-fCaq1sqkpjE \
jKDXpnc92H_eSVKaxvWOSbTNJpv2cT-Z6elbAkV_X \
CLdQWaJGR_KxNLQcSBiUGCBmTgZun8XQAB8b9G0eV \
SPiXZctbOn9VjctPtWjTTgD2tTGAWTlhoc6v4Yuqr \
glFnrQjEhfhrxofl304jgkfedZ7LoSAz0GQA8304J \
kXEUv6pzGbFquG4BQMiM82iNwSVIrxGYUuxwtb8dL \
23i_c6otMa3xkCnIfV0FiTQCBMMbgIQ_HiIGlZBve \
p7vumWRamCZcEiH-EZNIhT9GDNU5ZOuaPpvh9ZbJE \
s9fJyEzdhdxgRgOI9oPQkIqfs_M-HSiFw49VdR_vY \
thwG02ZkYJQj4PtrTMRTTiqigBTTd1jT6JO9qM0k_ \
6HFcO7TvuRob19ZKb7NBhSgT_10AjpZrTNO3hAwrj \
NyATt2L2hAqC5gJ6eGlgmZhzpeN1HEFaTwNtkA2VM \
Bk2ayax708HoCIWaYqa9g.g8om4HxFLGcsyPdXsDV \
Ciw

```

```json title="응답"
{
"version": "2024-06-01",
"traceId": "123fd1b012345e1fdf0123a012dcee5c",
"entityType": "seller",
"entityBody": {
  "id": "seller-1",
  "refSellerId": "my-seller-1",
  "businessType": "INDIVIDUAL_BUSINESS",
  "company": {
    "name": "테스트 상호",
    "representativeName": "김토스",
    "businessRegistrationNumber": "1234567890",
    "email": "toss@sample.com",
    "phone": "01012345678"
  },
  "individual": null,
  "account": {
    "bankCode": "092",
    "accountNumber": "123*****90123",
    "holderName": "김토스"
  },
  "status": "APPROVAL_REQUIRED",
  "metadata": {
    "key1": "value1",
    "key2": "value2"
  }
}
}

```

**개인 및 개인사업자 셀러는 본인인증이 필요해요**

셀러 등록 직후 셀러의 사업자 유형에 따라 상태(`status`)가 다른데요. 개인 및 개인사업자 셀러는 `APPROVAL_REQUIRED` 상태로 등록돼요. 개인 및 개인사업자에게는 `phone`으로 등록된 전화번호로 본인인증 문자가 발송됩니다. 본인인증을 성공적으로 마친 이후에만 상태가 `PARTIALLY_APPROVED`로 바뀌고, 해당 셀러에게 일주일에 1천만원 미만의 정산금을 지급대행할 수 있어요. 법인사업자는 등록 직후 일주일에 1천만원 미만의 정산금을 바로 지급대행할 수 있어요.

**셀러에게 일주일에 1천만원 이상 지급하려면 KYC 심사가 필요해요**

셀러에게 일주일에 1천만원 이상 지급대행을 시도하면 셀러의 상태가 `KYC_REQUIRED`로 바뀌고 셀러의 이메일 주소로 [KYC 심사](https://www.tosspayments.com/blog/articles/30357) 안내가 발송돼요. 해당 상태에서는 셀러에게 지급대행이 불가능합니다. [KYC](/resources/glossary/kyc) 심사가 통과하면 셀러의 상태는 `APPROVED`로 바뀌고, 금액 제한 없이 지급대행이 가능합니다. KYC 심사를 마친 셀러는 1년 또는 3년에 다시 심사를 받아야 되기 때문에 상태가 다시 `KYC_REQUIRED`로 바뀝니다.

셀러 유형과 상관없이 셀러에게 일주일에 1천만원 이상 지급하려면 해당 절차는 필수입니다.

셀러 고객확인제도(KYC) 정책 프로세스

**웹훅으로 셀러의 상태 변경을 받으세요**

셀러의 상태 변경은 [`seller.changed`](/reference/using-api/webhook-events#sellerchanged) 웹훅 이벤트로 받을 수 있어요.

```mermaid
---
title: 셀러 상태 흐름
---
%%{
init: {
  'themeVariables': {
    'fontSize': '14px',
    'fontFamily': 'Open Sans Soft Regular'
  }
}
}%%
flowchart TD
  classDef plain fill:#ffffff, stroke:#ffffff
  classDef noWebhook fill:#f3f4f6,stroke:#e9eaec
  classDef yesWebhook fill:#e8f2fe,stroke:#b9d7f1
  classDef hide fill:#0000,stroke:#0000,stroke-width:0px,color:#0000;
  classDef hide-font color:#0000;
  linkStyle default stroke:#4e5968, stroke-width:1px

  id4(셀러 등록):::noWebhook -->|법인사업자 셀러| id1:::noWebhook
  id4 -->|개인 및 개인사업자 셀러| id5([APPROVAL_REQUIRED]):::noWebhook
  id5 -->|본인인증| id1:::yesWebhook
  id1([PARTIALLY_APPROVED]) -->|1천만원 이상 지급 요청| id2([KYC_REQUIRED]):::yesWebhook
  id2 -->|KYC 심사 완료| id3([APPROVED]):::yesWebhook
  id3 -->|KYC 심사 재이행| id2

  %% Legend
  id2 ~~~ legend
  subgraph legend
      direction LR
      Y:::yesWebhook ~~~ |: 웹훅 전송| Z:::hide
  end
  class Y,Z,legend hide-font
  class legend hide
```



title: 셀러 정보를 수정할 수 있나요?


네. [셀러 수정 API](/reference/additional#셀러-수정)로 셀러의 정보를 수정할 수 있어요. 사업자번호는 수정할 수 없습니다. 거래하는 셀러가 폐업하고 새로운 사업자를 등록했다면, [셀러를 삭제](/reference/additional#셀러-삭제)하고 새로 등록해주세요.



title: KYC 심사는 왜 필요한가요?


[KYC(Know Your Customer, 고객확인)](/resources/glossary/kyc)는 **금융회사가 거래하는 구매자의 신원을 확인하고 구매자의 자금이 합법적인 경로로 취득된 것인지 확인하는 제도**입니다. [자금세탁방지](/resources/glossary/aml)의 첫걸음이기도 하고요.



title: KYC 심사를 미리 요청할 수 있나요?


아니요. 아직까지는 셀러에게 일주일 동안 1천만원 이상 지급을 요청하면 KYC 절차가 시작됩니다.



title: 테스트 환경에서 셀러를 등록할 수 있나요?


네, 테스트 환경에서는 유효한 사업자번호로 법인사업자(`CORPORATE`) [셀러만 등록](/reference/additional#셀러-등록)할 수 있어요. 셀러의 상태가 `KYC_REQUIRED`로 바뀔 수는 있지만 테스트 KYC 수행이 불가능하기 때문에 `APPROVED` 상태로 바뀔 수는 없습니다. 테스트 환경에서는 일주일에 1천만원 이상 지급을 시도하면 다시 해당 테스트 셀러에게 지급대행할 수 없다는 뜻입니다.

## 2. 정산 잔액 확인하기

등록한 셀러에게 지급대행을 요청하기 전에, 정산 잔액이 충분한지 확인해주세요. [잔액 조회 API](/reference/additional#잔액-조회)를 호출하면 셀러에게 지급할 수 있는 정산금(`availableAmount`)과 아직 지급할 수 없는 정산금(`pendingAmount`)을 확인할 수 있어요. 아직 지급할 수 없는 정산금은 매출이 일어났지만, 토스페이먼츠로부터 아직 정산 받지 않은 금액입니다. 토스페이먼츠와 계약한 정산 주기에 따라 지급할 수 있는 정산금으로 전환됩니다.

잔액 조회에 성공하면 아래와 같은 [Balance 객체](/reference/additional#balance-객체)가 응답됩니다. 현재 지급대행은 `KRW` 통화만 지원합니다.

```json title="응답"
{
  "version": "2024-06-01",
  "traceId": "087fd7b452385e1fdf8123a123dcee5c",
  "entityType": "balance",
  "entityBody": {
    "pendingAmount": {
      "currency": "KRW",
      "value": 10000.0
    },
    "availableAmount": {
      "currency": "KRW",
      "value": 20000.0
    }
  }
}
```



title: 잔액 조회 API도 암호화해야 되나요?


아니요. 지급대행 서비스의 POST 요청만 암호화하면 됩니다. 잔액 조회는 GET 요청이기 때문에 암호화가 필요하지 않아요.



title: 정산주기를 바꿀 수 있나요?


계약과 관련된 정확한 안내는 토스페이먼츠 고객센터(1544-7772, 평일 오전 8시~오후 7시)에서 받을 수 있어요.



title: 테스트 환경에서 잔액 조회를 할 수 있나요?


네. 하지만 테스트 환경에서 [잔액조회 API](/reference/additional#잔액-조회)를 호출하면 응답으로 `availableAmount`만 돌아옵니다.

## 3. 지급대행 요청하기

셀러에게 지급할 정산 금액을 확인했다면 [지급대행 요청 API](/reference/additional#지급대행-요청)를 호출해주세요. 최대 100건의 지급대행 요청을 한 번에 보낼 수 있어요. 1건의 지급대행 요청에는 10억 미만의 정산 금액을 이체할 수 있습니다. 지급대행으로 셀러에게 지급한 정산 금액은 회수하기 어렵습니다. 토스페이먼츠는 지급 관련 책임이 없기 때문에 지급대행 서비스를 신중하게 사용해주세요.

[요청 파라미터](/reference/additional#지급대행-요청)를 확인해주세요. 토스페이먼츠 지급대행에는 두 가지 `scheduleType`이 있어요.

`EXPRESS`는 바로지급이며, 요청 당일에 셀러에게 정산금이 지급돼요. 지급 요청(`REQUESTED`) 후 배치 시간에 맞춰 지급 처리가 시작(`IN_PROGRESS`)돼요. 영업일 08:00~15:00 사이에만 요청할 수 있고, 휴일·공휴일에는 바로지급 요청 시 오류가 발생해요.

`SCHEDULED`는 예약 지급이며, `payoutDate` 파라미터로 설정한 날짜에 정산금이 지급돼요. 예약 지급은 익일 오전 9시부터 1년 이내의 영업일로만 설정할 수 있어요. 휴일·공휴일 또는 과거 일자를 입력하면 오류가 발생해요.

\* [지급대행 요청 API](/reference/additional#지급대행-요청)를 라이브 환경에서 테스트하면 수수료가 부과됩니다.

```json title="JSON" maxHeight="386px"
[
    {
      "refPayoutId": "my-payout-1",
      "destination": "seller-1",
      "scheduleType": "SCHEDULED",
      "payoutDate": "2024-08-08",
      "amount": {
        "currency": "KRW",
        "value": 5000
      },
      "transactionDescription": "8월대금지급",
      "metadata": {
        "key1": "value1",
        "key2": "value2"
      }
    }
]
```

```plain title="JWE"
// JWE로 암호화된 Request Body
eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwia \
WF0IjoiMjAyNC0wMS0wMVQwMDowMDowMCswOTowMC \
IsIm5vbmNlIjoiaGVsbG8ifQ..oiWf7Rvd0Ze3k2- \
3.nAO0bcdbjQkr_0A93NEtVIVFq4oOl16wbE1doIZ \
3DTQt-wdC9_bcAL4kHQW-J1tjIJ5cs8CaTRGKryIO \
Sqnhzks0EyinW7MxjxQNcElTnjcAbu-xTKFRl7iQg \
b0ZNO9biewwaHFGs_Uy4dWFpM2ysnrWeRv2zse5qa \
pkSecAS0kS0AVeRqb9G3bCUxvdubaJCmujOivmkfX \
ViRAYpC9eXKNrRpQt_cO7z_Av-Q-apPseS-0TpdWN \
G-z5A2UUmXq2RyyHI03bdPpNXHJxbpgJKG2EL-aGm \
eW4SA2MBGaJZwg0lJUk3lBWqB-y9qt1wednCNtDEe \
SxpYd9eP4Y40fKKBVq5FYYsDmmEhAs1_U-ApR9E3D \
gWi8J8YglAB40.igv4SyGYc2w0pvVmLO1Gug

```

Request Body 데이터는 보안 키로 [암호화](/guides/v2/payouts#암호화)해서 전송해주세요. JWE로 암호화한 Request Body를 API 요청의 데이터값으로 넣고 `TossPayments-api-security-mode: ENCRYPTION` 헤더를 추가하세요. **API 개별 연동 키 > 시크릿 키**를 이용하는 [Basic 인증](/reference/using-api/authorization#인증-헤더)도 반드시 해주세요.

[멱등키](/reference/using-api/authorization#멱등키-헤더)를 요청 헤더에 추가하면 중복 지급 없이 안전하게 처리됩니다. 하지만 멱등한 요청에서 에러가 반환되었을 때 멱등키를 변경해서 동일한 요청을 재시도하는 것은 위험이 있습니다. 정확한 오류 원인을 토스페이먼츠로부터 확인한 다음에 다시 요청을 보내주세요.

```bash title="요청"
curl --request POST \
--url https://api.tosspayments.com/v2/sellers \
--header 'Authorization: Basic base64({SECRET_KEY}:)' \
--header 'Content-Type: text/plain' \
--header 'TossPayments-api-security-mode: ENCRYPTION' \
--data 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiaWF0IjoiMjAyNC0wMS0wMVQwMDowMDowMCswOTowMCIsIm5vbmNlIjoiaGVsbG8ifQ..oiWf7Rvd0Ze3k2-3.nAO0bcdbjQkr_0A93NEtVIVFq4oOl16wbE1doIZ3DTQt-wdC9_bcAL4kHQW-J1tjIJ5cs8CaTRGKryIOSqnhzks0EyinW7MxjxQNcElTnjcAbu-xTKFRl7iQgb0ZNO9biewwaHFGs_Uy4dWFpM2ysnrWeRv2zse5qapkSecAS0kS0AVeRqb9G3bCUxvdubaJCmujOivmkfXViRAYpC9eXKNrRpQt_cO7z_Av-Q-apPseS-0TpdWNG-z5A2UUmXq2RyyHI03bdPpNXHJxbpgJKG2EL-aGmeW4SA2MBGaJZwg0lJUk3lBWqB-y9qt1wednCNtDEeSxpYd9eP4Y40fKKBVq5FYYsDmmEhAs1_U-ApR9E3DgWi8J8YglAB40.igv4SyGYc2w0pvVmLO1Gug'
```

지급대행이 성공적으로 요청되면 아래와 같이 JWE로 암호화된 Payout 객체가 돌아옵니다. JWE를 [복호화](#복호화)해서 응답 값을 확인해주세요.

지급대행 요청에 실패하면 JWE로 암호화된 [에러 객체](/reference/using-api/req-res#api-v2-에러-객체)가 돌아옵니다. 지급대행 요청에 실패하면 요청 건 모두 실패하고 요청 건 중에 첫 번째 에러에 대한 에러 객체가 돌아옵니다. 예를 들어 지급대행 요청 3번, 51번에 오류가 났다면 요청은 전체 실패하고 3번에 대한 에러 객체가 응답돼요.

```plain title="JWE"
// JWE로 암호화된 응답
eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwia \
WF0IjoiMjAyNC0wMS0wMVQwMDowMDowMCswOTowMC \
IsIm5vbmNlIjoiaGVsbG8ifQ..BfcnvvETEErj0A4 \
l.E1wHqdr1jjIhhpa8RnVut9ZgxJ3MnMlRTSaEwbj \
yVNDfFhIhaeyCM2Jm6pVPrAgepDFAB6jeZ8PvS45g \
I5IWiH8FvgQLinIh-fQLWIlBNJ1vlBbBKwWMbUi8_ \
z2Yxl-mSTlq36CpJiqcYjNzYWQI6ltl2QHeXUQuzW \
DHaYGYZVZT8AIShC5EUyQN9qIKRsnV5gwUZgV75gM \
NWsFH2aiePVVHjURXdfb1muiC7KZaZnfJTtZpx-dN \
ayIS4aal-aulEWg8HminKJEIqcRq86BzSttXvj76l \
r3W3c83phJ1w3I3LiLnHNaXG5RUplJdkOASuSrsCd \
8ED_pcBRN0modR-A7uWNw6n-k71tPbuZEPwnXbXSV \
Sa_7DdmkR05SGna7nz-Tobuf2QyawlT1D_oss2Epu \
490VK-MVsp_1jlaxb2UGHmTgOe-vgYvZkIqVwrzv0 \
wymXll2QY0Y5ZWeI5W5bHNeR2o7qjhzxKQPIN2utS \
W3ENX1uGgxJngkAS21snDnOEzfrMxjyz9pgvd9pYo \
v3e0FZrt6iJ424Z74vs-hpivRFyVfbRNwiQCHAkFQ \
JbcxRNJ16n49PEc3jiidWl1y6J-_HkTgFLU7PtHkx \
HrSQ7pXEHaQyB9atEshau7ciF-X-WyMCrdzQTiUcd \
famf8JwRS1DX9uKZ01.-hIGuKWgFaePf9yg7NHoTg \

```

```json title="JSON" maxHeight="524px"
{
"version": "2024-06-01",
"traceId": "13da4a1212d21f8b123bf90cb0da456d",
"entityType": "payout-list",
"entityBody": {
  "items": [
    {
      "id": "FPA_12345",
      "refPayoutId": "my-payout-1",
      "destination": "seller-1",
      "scheduleType": "SCHEDULED",
      "payoutDate": "2024-08-08",
      "amount": {
        "currency": "KRW",
        "value": 5000.0
      },
      "transactionDescription": "8월대금지급",
      "requestedAt": "2024-08-07T22:00:00+09:00",
      "status": "REQUESTED",
      "error": null,
      "metadata": {
        "key1": "value1",
        "key2": "value2"
      }
    }
  ]
}
}

```

**은행 오류로 지급대행이 실패할 수 있어요**

하지만 지급대행 요청이 끝났다고 지급대행이 항상 성공하지는 않아요. 한도 초과 또는 원천사 점검 중에는 지급대행이 실패해요. 그런 경우에는 Payout 객체의 상태는 `FAILED`로 바뀌고, `error` 필드에서 에러를 확인할 수 있어요. 지급대행의 최종 성공 및 실패 상태는 [`payout.changed`](/reference/using-api/webhook-events) 웹훅 이벤트를 등록해서 받을 수 있습니다.

만약에 지급대행 요청이 은행 오류로 실패하는 케이스를 테스트하고 싶다면 아래 계좌번호를 등록해보세요. 아래 계좌를 셀러에 등록하고, 해당 셀러에게 지급대행을 요청해주세요.

| 실패 재현 계좌번호 | 은행(은행코드)     |
| ------------------ | ------------------ |
| `77701777777`      | 우리종합금융 (295) |
| `3025353430761`    | 농협 (011)         |
| `02004240994312`   | 산업은행 (002)     |

**예약 지급대행 요청만 취소할 수 있어요**

지급대행 요청은 `REQUESTED` 상태일 때 [요청을 취소](/reference/additional#지급대행-요청-취소)할 수 있어요. 지급대행 요청의 상태 변화는 [`payout.changed`](/reference/using-api/webhook-events) 웹훅으로 확인해주세요.

```mermaid
---
title: 지급대행 요청 흐름
---
%%{
  init: {
    'themeVariables': {
      'fontSize': '14px',
      'fontFamily': 'Open Sans Soft Regular'
    }
  }
}%%

flowchart TD
    classDef plain fill:#ffffff, stroke:#ffffff
    classDef noWebhook fill:#f3f4f6,stroke:#e9eaec
    classDef yesWebhook fill:#e8f2fe,stroke:#b9d7f1
    classDef hide fill:#0000,stroke:#0000,stroke-width:0px,color:#0000;
    classDef hide-font color:#0000;
    linkStyle default stroke:#4e5968, stroke-width:1px

    %% ENTRY
    id1(지급 요청):::noWebhook -->|EXPRESS| id3([REQUESTED]):::noWebhook
    id1 -->|SCHEDULED| id3

    %% REQUESTED
    id3 -->|지급대행 요청 취소| id4([CANCELED]):::noWebhook
    id3 -->|KYC 이슈| id4([CANCELED]):::noWebhook

    id3 -->|처리 시작| id2([IN_PROGRESS]):::noWebhook

    %% IN_PROGRESS
    id2 -->|성공| id5([COMPLETED]):::yesWebhook
    id2 -->|실패| id6([FAILED]):::yesWebhook

    %% Legend
    id4 ~~~ legend
    subgraph legend
        direction LR
        Y:::yesWebhook ~~~ |: 웹훅 전송| Z:::hide
    end
    class Y,Z,legend hide-font
    class legend hide
```
