Java

Java 택배API 연동 가이드

HttpClient / OkHttp를 사용한 택배 조회 및 Spring Boot 웹훅
JavaScript Python Java PHP C#
이 가이드에서 다루는 내용
• Java 11+ HttpClient를 사용한 API 호출
• OkHttp 라이브러리 예제
• Spring Boot 웹훅 컨트롤러
• 에러 처리 및 재시도 로직

1. 의존성 추가

Maven (pom.xml)

<dependencies>
    <!-- JSON 파싱 -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.10.1</version>
    </dependency>

    <!-- OkHttp (선택) -->
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>4.12.0</version>
    </dependency>
</dependencies>

Gradle (build.gradle)

dependencies {
    implementation 'com.google.code.gson:gson:2.10.1'
    implementation 'com.squareup.okhttp3:okhttp:4.12.0'
}

2. 환경 변수 설정

// application.properties (Spring Boot)
delivery.api.public-key=${DELIVERY_PUBLIC_KEY}
delivery.api.secret-key=${DELIVERY_SECRET_KEY}
delivery.api.base-url=https://api.deliveryapi.co.kr/v1
보안 주의
Secret Key를 코드에 직접 하드코딩하지 마세요. 환경 변수나 시크릿 매니저를 사용하세요.

3. 배송 조회 (Java 11+ HttpClient)

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import com.google.gson.Gson;
import com.google.gson.JsonObject;

public class DeliveryApiClient {
    private static final String BASE_URL = "https://api.deliveryapi.co.kr/v1";
    private final String publicKey;
    private final String secretKey;
    private final HttpClient httpClient;
    private final Gson gson;

    public DeliveryApiClient(String publicKey, String secretKey) {
        this.publicKey = publicKey;
        this.secretKey = secretKey;
        this.httpClient = HttpClient.newHttpClient();
        this.gson = new Gson();
    }

    public JsonObject trackDelivery(String courierCode, String trackingNumber) throws Exception {
        String url = BASE_URL + "/tracking/trace";

        // JSON 페이로드 생성
        String jsonBody = String.format(
            "{\"items\":[{\"courierCode\":\"%s\",\"trackingNumber\":\"%s\"}]}",
            courierCode, trackingNumber
        );

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(url))
                .header("Authorization", "Bearer " + publicKey + ":" + secretKey)
                .header("Content-Type", "application/json")
                .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
                .build();

        HttpResponse<String> response = httpClient.send(
                request,
                HttpResponse.BodyHandlers.ofString()
        );

        if (response.statusCode() != 200) {
            throw new RuntimeException("API Error: " + response.body());
        }

        JsonObject fullResponse = gson.fromJson(response.body(), JsonObject.class);
        // results 배열의 첫 번째 결과 반환
        return fullResponse.getAsJsonObject("data").getAsJsonArray("results").get(0).getAsJsonObject();
    }

    public static void main(String[] args) throws Exception {
        String publicKey = System.getenv("DELIVERY_PUBLIC_KEY");
        String secretKey = System.getenv("DELIVERY_SECRET_KEY");
        DeliveryApiClient client = new DeliveryApiClient(publicKey, secretKey);

        JsonObject result = client.trackDelivery("cj", "1234567890");

        if (result.get("success").getAsBoolean()) {
            JsonObject data = result.getAsJsonObject("data");
            System.out.println("배송 상태: " + data.get("deliveryStatus").getAsString());
        } else {
            System.out.println("조회 실패: " + result.get("message").getAsString());
        }
    }
}

4. OkHttp 사용 예제

import okhttp3.*;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.io.IOException;

public class DeliveryApiOkHttp {
    private static final String BASE_URL = "https://api.deliveryapi.co.kr/v1";
    private final String publicKey;
    private final String secretKey;
    private final OkHttpClient client;
    private final Gson gson;

    public DeliveryApiOkHttp(String publicKey, String secretKey) {
        this.publicKey = publicKey;
        this.secretKey = secretKey;
        this.client = new OkHttpClient();
        this.gson = new Gson();
    }

    public JsonObject trackDelivery(String courierCode, String trackingNumber) throws IOException {
        String jsonBody = String.format(
            "{\"items\":[{\"courierCode\":\"%s\",\"trackingNumber\":\"%s\"}]}",
            courierCode, trackingNumber
        );

        RequestBody body = RequestBody.create(
                jsonBody,
                MediaType.parse("application/json")
        );

        Request request = new Request.Builder()
                .url(BASE_URL + "/tracking/trace")
                .addHeader("Authorization", "Bearer " + publicKey + ":" + secretKey)
                .addHeader("Content-Type", "application/json")
                .post(body)
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("API Error: " + response.body().string());
            }
            JsonObject fullResponse = gson.fromJson(response.body().string(), JsonObject.class);
            return fullResponse.getAsJsonObject("data").getAsJsonArray("results").get(0).getAsJsonObject();
        }
    }
}

5. DTO 클래스

import java.util.List;

public class TrackingResult {
    private boolean success;
    private TrackingData data;

    // Getters and Setters
    public boolean isSuccess() { return success; }
    public TrackingData getData() { return data; }
}

public class TrackingData {
    private String courierCode;
    private String courierName;
    private String trackingNumber;
    private String deliveryStatus;
    private String deliveryStatusText;
    private boolean isDelivered;
    private List<Progress> progresses;

    // Getters and Setters
}

public class Progress {
    private String dateTime;
    private String status;
    private String statusCode;
    private String location;
    private String description;

    // Getters and Setters
}

// 사용 예시
TrackingResult result = gson.fromJson(responseBody, TrackingResult.class);
System.out.println("상태: " + result.getData().getDeliveryStatusText());

6. Spring Boot 웹훅 컨트롤러

import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.HexFormat;

@RestController
@RequestMapping("/webhook")
public class DeliveryWebhookController {

    @Value("${delivery.webhook.secret}")
    private String webhookSecret;

    @PostMapping("/delivery")
    public ResponseEntity<?> handleWebhook(
            @RequestBody String payload,
            @RequestHeader("X-Webhook-Signature") String signature,
            @RequestHeader("X-Webhook-Timestamp") String timestamp
    ) {
        // 서명 검증
        if (!verifySignature(payload, signature, timestamp)) {
            return ResponseEntity.status(401)
                    .body(Map.of("error", "Invalid signature"));
        }

        // JSON 파싱
        Gson gson = new Gson();
        JsonObject json = gson.fromJson(payload, JsonObject.class);
        String event = json.get("event").getAsString();
        JsonArray items = json.getAsJsonArray("items");

        for (JsonElement element : items) {
            JsonObject item = element.getAsJsonObject();
            switch (event) {
                case "tracking.polled":
                    handleStatusChanged(item);
                    break;
                case "tracking.completed":
                    handleDelivered(item);
                    break;
            }
        }

        return ResponseEntity.ok(Map.of("received", true));
    }

    private boolean verifySignature(String payload, String signature, String timestamp) {
        try {
            // timestamp 유효성 검증 (5분 허용)
            long now = System.currentTimeMillis() / 1000;
            if (Math.abs(now - Long.parseLong(timestamp)) > 300) {
                return false;
            }
            // 서명 대상: timestamp.payload
            String signedPayload = timestamp + "." + payload;
            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(
                    webhookSecret.getBytes(),
                    "HmacSHA256"
            );
            mac.init(secretKey);
            byte[] hash = mac.doFinal(signedPayload.getBytes());
            String expected = HexFormat.of().formatHex(hash);
            // 헤더값: sha256={hex} → prefix 제거 후 비교
            String received = signature.replace("sha256=", "");
            return expected.equals(received);
        } catch (Exception e) {
            return false;
        }
    }

    private void handleStatusChanged(JsonObject data) {
        String trackingNumber = data.get("trackingNumber").getAsString();
        String status = data.get("currentStatus").getAsString();
        System.out.println("배송 상태 변경: " + trackingNumber + " -> " + status);
        // 비즈니스 로직 처리
    }

    private void handleDelivered(JsonObject data) {
        String trackingNumber = data.get("trackingNumber").getAsString();
        System.out.println("배송 완료: " + trackingNumber);
        // 배송 완료 처리
    }
}

7. 에러 처리 및 재시도

import java.util.concurrent.TimeUnit;

public class DeliveryApiWithRetry {
    private final DeliveryApiClient client;
    private final int maxRetries;

    public DeliveryApiWithRetry(String apiKey, int maxRetries) {
        this.client = new DeliveryApiClient(apiKey);
        this.maxRetries = maxRetries;
    }

    public JsonObject trackWithRetry(String courier, String trackingNumber) throws Exception {
        Exception lastException = null;

        for (int attempt = 1; attempt <= maxRetries; attempt++) {
            try {
                return client.trackDelivery(courier, trackingNumber);
            } catch (Exception e) {
                lastException = e;

                // 재시도 가능 여부 확인
                if (isRetryable(e) && attempt < maxRetries) {
                    long delay = (long) Math.pow(2, attempt) * 1000; // 지수 백오프
                    System.out.printf("재시도 %d/%d, %dms 후...%n", attempt, maxRetries, delay);
                    TimeUnit.MILLISECONDS.sleep(delay);
                    continue;
                }

                throw e;
            }
        }

        throw lastException;
    }

    private boolean isRetryable(Exception e) {
        // 네트워크 에러 또는 5xx 서버 에러
        String message = e.getMessage();
        return message.contains("5") || message.contains("timeout") || message.contains("connect");
    }
}

다른 언어 가이드

지금 바로 시작하세요!

무료 플랜으로 API를 바로 테스트해보세요

무료로 시작하기 →