Payout
Initiate payouts from your Shinhan account to any bank operating within Vietnam.

Quick Reference
| Endpoint | Method | Description |
|---|---|---|
/api/v1/payouts | POST | Create a new payout request |
/api/v1/payouts | GET | List all payout requests |
/api/v1/payouts/:id | GET | Get specific payout request |
/api/v1/payouts/:id/otps | POST | Verify OTP for payout |
Enterprise Only
Payout API is available for Enterprise Solutions only. Contact [email protected] for access.
Setup: Whitelist IP
Before using the Payout API, you must whitelist your server IP address.
-
Go to book.finan.one/setting → Open API

-
Click Whitelist IP

-
Enter your IP address and save

warning
Once an IP is whitelisted, the portal Cashout flow is disabled. All payouts must use the API.
API Endpoints
Create Payout Request
Create a new payout request. Save the returned id for OTP verification.
POST /api/v1/payouts
- cURL
- Go
curl -X POST 'https://api.finan.one/open/api/v1/payouts' \
-H 'Content-Type: application/json' \
-H 'x-client-id: YOUR_CLIENT_ID' \
-H 'x-signature: YOUR_SIGNATURE' \
-H 'x-timestamp: 1699999999' \
-d '{
"account_id": "54957437-0cb5-4992-ad0e-76d26ba4ddc3",
"amount": 6000000,
"beneficiary_account_name": "NGUYEN VAN A",
"beneficiary_account_number": "1234567890",
"beneficiary_bank_id": "161",
"currency": "VND",
"reference_id": "PAYOUT-001",
"description": "thanh toan don hang"
}'
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"time"
)
type CreatePayoutRequest struct {
AccountID string `json:"account_id"`
Amount int64 `json:"amount"`
BeneficiaryAccountName string `json:"beneficiary_account_name"`
BeneficiaryAccountNumber string `json:"beneficiary_account_number"`
BeneficiaryBankID string `json:"beneficiary_bank_id"`
Currency string `json:"currency,omitempty"`
ReferenceID string `json:"reference_id,omitempty"`
Description string `json:"description"`
}
func generateSignature(secretKey, method, path, payload, timestamp string) string {
message := secretKey + "_" + method + "_" + path + "_" + payload + "_" + timestamp
hash := sha256.Sum256([]byte(message))
return hex.EncodeToString(hash[:])
}
func main() {
clientID := "YOUR_CLIENT_ID"
secretKey := "YOUR_SECRET_KEY"
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
reqBody := CreatePayoutRequest{
AccountID: "54957437-0cb5-4992-ad0e-76d26ba4ddc3",
Amount: 6000000,
BeneficiaryAccountName: "NGUYEN VAN A",
BeneficiaryAccountNumber: "1234567890",
BeneficiaryBankID: "161",
Currency: "VND",
ReferenceID: "PAYOUT-001",
Description: "thanh toan don hang",
}
jsonBody, _ := json.Marshal(reqBody)
signature := generateSignature(secretKey, "POST", "/api/v1/payouts", string(jsonBody), timestamp)
req, _ := http.NewRequest("POST", "https://api.finan.one/open/api/v1/payouts", bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-client-id", clientID)
req.Header.Set("x-signature", signature)
req.Header.Set("x-timestamp", timestamp)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
account_id | uuid | ✅ | Source Shinhan account ID. Get from Account API |
amount | integer | ✅ | Amount in smallest currency unit |
beneficiary_account_name | string | ✅ | Recipient's name as registered with bank |
beneficiary_account_number | string | ✅ | Recipient's account number |
beneficiary_bank_id | string | ✅ | Bank code. See Code Reference |
currency | string | ISO currency code (default: VND) | |
reference_id | string | Your unique transaction ID for tracking | |
description | string | ✅ | Transfer note (max 30 characters) |
Response
- ✅ Success (200)
- ❌ Error
{
"message": { "content": "Thực thi API thành công" },
"code": 102001,
"request_id": "abc123...",
"data": {
"id": "660e8400-e29b-41d4-a716-446655440001",
"status": "waiting_confirm",
"remaining_try": 3
}
}
| Code | Message | Description |
|---|---|---|
| 400 | Bad Request | Invalid request body |
| 401 | Unauthorized | Invalid authentication |
| 403 | Forbidden | IP not whitelisted |
| 404 | Not Found | Account ID not found |
| 422 | Unprocessable Entity | Insufficient balance or validation failed |
{
"message": {
"content": "Yêu cầu không hợp lệ",
"error": "IP address not whitelisted"
},
"code": 104000,
"request_id": "abc123..."
}
Payout Status
| Status | Description |
|---|---|
waiting_confirm | Awaiting OTP verification |
processing | OTP verified, transfer in progress |
success | Payout completed |
failed | Payout failed |
Get Payout Requests
Retrieve all payout requests.
GET /api/v1/payouts
- cURL
- Go
curl -X GET 'https://api.finan.one/open/api/v1/payouts' \
-H 'Content-Type: application/json' \
-H 'x-client-id: YOUR_CLIENT_ID' \
-H 'x-signature: YOUR_SIGNATURE' \
-H 'x-timestamp: 1699999999'
package main
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"strconv"
"time"
)
func generateSignature(secretKey, method, path, payload, timestamp string) string {
message := secretKey + "_" + method + "_" + path + "_" + payload + "_" + timestamp
hash := sha256.Sum256([]byte(message))
return hex.EncodeToString(hash[:])
}
func main() {
clientID := "YOUR_CLIENT_ID"
secretKey := "YOUR_SECRET_KEY"
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
signature := generateSignature(secretKey, "GET", "/api/v1/payouts", "", timestamp)
req, _ := http.NewRequest("GET", "https://api.finan.one/open/api/v1/payouts", nil)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-client-id", clientID)
req.Header.Set("x-signature", signature)
req.Header.Set("x-timestamp", timestamp)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
Response
- ✅ Success (200)
- ❌ Error
{
"message": { "content": "Thực thi API thành công" },
"code": 102000,
"request_id": "abc123...",
"data": [
{
"id": "660e8400-e29b-41d4-a716-446655440001",
"status": "success",
"amount": 6000000,
"beneficiary_account_name": "NGUYEN VAN A",
"beneficiary_account_number": "1234567890",
"beneficiary_bank_id": "161",
"reference_id": "PAYOUT-001",
"remaining_try": 0,
"created_at": "2024-01-20T14:00:00Z"
}
]
}
| Code | Message | Description |
|---|---|---|
| 401 | Unauthorized | Invalid authentication |
| 403 | Forbidden | IP not whitelisted |
Get Single Payout
GET /api/v1/payouts/:payout_id
Verify OTP
Submit the OTP code to confirm a payout request.
POST /api/v1/payouts/:payout_id/otps
- cURL
- Go
curl -X POST 'https://api.finan.one/open/api/v1/payouts/660e8400-e29b-41d4-a716-446655440001/otps' \
-H 'Content-Type: application/json' \
-H 'x-client-id: YOUR_CLIENT_ID' \
-H 'x-signature: YOUR_SIGNATURE' \
-H 'x-timestamp: 1699999999' \
-d '{
"code": 123456
}'
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"time"
)
type VerifyOTPRequest struct {
Code int `json:"code"`
}
func generateSignature(secretKey, method, path, payload, timestamp string) string {
message := secretKey + "_" + method + "_" + path + "_" + payload + "_" + timestamp
hash := sha256.Sum256([]byte(message))
return hex.EncodeToString(hash[:])
}
func main() {
clientID := "YOUR_CLIENT_ID"
secretKey := "YOUR_SECRET_KEY"
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
payoutID := "660e8400-e29b-41d4-a716-446655440001"
reqBody := VerifyOTPRequest{
Code: 123456,
}
jsonBody, _ := json.Marshal(reqBody)
path := "/api/v1/payouts/" + payoutID + "/otps"
signature := generateSignature(secretKey, "POST", path, string(jsonBody), timestamp)
url := "https://api.finan.one/open/api/v1/payouts/" + payoutID + "/otps"
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-client-id", clientID)
req.Header.Set("x-signature", signature)
req.Header.Set("x-timestamp", timestamp)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
code | integer | ✅ | OTP code received from bank |
Response
- ✅ Success (200)
- ❌ Error
{
"message": { "content": "Thực thi API thành công" },
"code": 102000,
"request_id": "abc123...",
"data": {
"id": "660e8400-e29b-41d4-a716-446655440001",
"status": "processing",
"remaining_try": 2
}
}
| Code | Message | Description |
|---|---|---|
| 400 | Bad Request | Invalid OTP format |
| 401 | Unauthorized | Invalid authentication |
| 404 | Not Found | Payout request not found |
| 422 | Unprocessable Entity | Invalid OTP or no retries remaining |
{
"message": {
"content": "Yêu cầu không hợp lệ",
"error": "Invalid OTP code"
},
"code": 104000,
"request_id": "abc123..."
}
OTP Attempts
Each payout request has a limited number of OTP attempts (typically 3). After exhausting all attempts, create a new payout request.
Payout Flow
1. Create Payout Request → status: waiting_confirm
2. Receive OTP from bank (SMS/Email)
3. Submit OTP → status: processing
4. Payout completes → status: success
Next Steps
- Code Reference - Bank codes for
beneficiary_bank_id - Account - Get your
account_id - Payment Webhook - Webhook retry feature