WebView redirect POST→GET
KakaoPay 결제수단 등록 확인 과정에서 WebView의 redirect 동작 문제를 발견하고 해결한 경험을 기록한다. 모바일 WebView에서 POST redirect가 동작하지 않는 환경이 존재한다는 사실은 문서에 나와 있지 않고, 실제 테스트에서만 발견할 수 있는 문제였다.
왜 redirect 방식이 핵심이었나
간편결제를 앱 안에서 연동하려면 중간에 인증 결과를 받아 저장할 서버 콜백 endpoint가 필요했다. 사용자가 KakaoPay 인증을 마치고 돌아오면, 우리 서버는 그 결과를 받아 저장결제 등록 상태를 반영해야 했고, 그 다음에는 다시 앱 딥링크(앱 스키마)를 호출해 사용자를 원래 앱 흐름으로 돌려보내야 했다. 이 복귀 경로는 iOS와 Android를 모두 고려해야 했기 때문에, 서버 저장만 맞춘다고 끝나는 작업이 아니었다.
앱에서 등록을 시작한다
사용자가 Nike 앱이나 SNKRS 앱에서 저장결제 등록을 요청한다.
WebView로 KakaoPay 인증 화면을 연다
앱 내부 WebView가 KakaoPay 인증 페이지로 이동하고, 사용자는 그 안에서 인증을 진행한다.
인증 결과가 서버 callback endpoint로 돌아온다
인증이 끝나면 KakaoPay가 우리 서버의 callback endpoint로 결과를 돌려준다.
서버가 등록 상태를 저장한다
callback에서 받은 식별자와 결과를 기준으로 저장결제 등록 상태를 반영한다.
앱 딥링크(앱 스키마)를 다시 호출한다
서버 처리가 끝나면 앱 딥링크(앱 스키마)를 호출해 사용자를 원래 앱 흐름으로 돌려보낸다. 이때 iOS와 Android에서 모두 자연스럽게 복귀하는지까지 확인해야 했다.
문제는 이 흐름에서 3번 단계의 redirect endpoint가 POST를 전제로 설계되어 있었다는 점이었다.
모바일 WebView에서는 무엇이 달랐나
PC 브라우저에서는 POST redirect가 정상 동작했지만, 특정 모바일 앱의 WebView에서는 POST redirect가 GET으로 변환되거나 아예 동작하지 않는 경우가 있었다. HTTP 표준(RFC 7231)에 따르면 302 redirect 시 POST를 GET으로 변환하는 것은 유효한 동작이고, 실제로 많은 WebView 구현이 이렇게 동작한다.
핵심 체크포인트
- 초기 구현 단계: WebView에서 KakaoPay 등록 확인을 처리하는 최초 흐름을 구현했다. 이때는 POST redirect를 전제로 설계했다.
- QA 단계: 일부 모바일 환경에서 등록 완료 후 redirect가 실패하는 사례를 발견했다. 원인을 추적해보니 POST redirect가 GET으로 바뀌면서 요청 body가 사라지는 경우가 있었다.
- 최종 대응은 redirect 기준을 GET으로 정리하고, GET/POST 모두 수용할 수 있게 확장하는 것이었다.
호환성 문제를 어떻게 우회했나
수정 자체는 간단했다 — endpoint를 GET으로도 받을 수 있게 하고, 필요한 파라미터를 URL query string으로 전달하는 방식으로 변경. 하지만 이 문제를 발견하기까지의 과정이 중요하다.
PC 브라우저 테스트에서는 POST redirect가 정상 동작했다. QA 팀의 모바일 테스트에서 특정 앱(Nike App 또는 SNKRS App)의 WebView에서만 문제가 발생했다. WebView의 redirect 동작은 앱이 사용하는 WebView 엔진(Android WebView, iOS WKWebView)과 설정에 따라 달라진다.
결제 연동에서 WebView redirect는 매우 흔한 패턴이다. PG사(Payment Gateway) 인증, 3DS 인증, 간편결제 인증 모두 WebView redirect를 사용한다. 이때 redirect method를 GET으로 설계하는 것이 가장 안전한 선택이다. POST가 필요한 데이터는 서버 간 통신으로 처리하고, WebView redirect는 결과 확인 용도로만 사용하는 것이 바람직하다.
특히 앱 연동에서는 서버 callback 이후의 마지막 한 단계도 중요했다. 서버가 인증 결과를 저장한 뒤 앱 딥링크(앱 스키마)를 다시 호출해주지 않으면, 사용자는 인증은 끝냈는데 앱으로 자연스럽게 돌아오지 못한다. 결국 이 문제는 단순한 redirect method 이슈가 아니라 서버 저장 + 앱 복귀까지 묶인 사용자 흐름의 문제였다.
결제 연동에서 브라우저/앱 환경의 다양성을 과소평가하면 안 된다. 특히 WebView는 네이티브 브라우저와 동작이 다를 수 있고, OS 버전과 앱 버전에 따라서도 달라진다. 이후 결제 연동에서는 WebView redirect를 GET 기반으로 설계하는 것을 원칙으로 삼았다.
QA에서 재현 조건을 먼저 잡았다
문제가 나는 앱/WebView 조합을 특정하고 POST가 실제로 GET으로 바뀌는지 확인했다.
완료 경로를 GET 기준으로 다시 설계했다
결과 확인용 redirect는 GET으로 통일하고, 필요한 데이터는 서버 쪽에서 검증하도록 나눴다.
앱 환경별로 다시 검증했다
브라우저뿐 아니라 실제 모바일 WebView에서 등록 완료와 iOS/Android 앱 복귀 흐름까지 다시 확인했다.
리다이렉트 예시 코드
모바일 WebView 호환성을 위해 callback을 처리한 뒤 등록 상태를 저장하고, 마지막에 앱 딥링크(앱 스키마)로 다시 복귀시키는 패턴 예시다.
@GetMapping("/payment/complete")
public ResponseEntity<Void> complete(@RequestParam("token") String token) {
String safeToken = signer.verify(token);
registrationService.markCompleted(safeToken);
URI target = URI.create("nike://payment/result?token=" + safeToken);
return ResponseEntity.status(302).location(target).build();
}
@PostMapping("/payment/callback")
public ResponseEntity<Void> callback(@RequestBody CallbackPayload payload) {
String token = signer.sign(payload.registrationId());
return ResponseEntity.status(302).location(URI.create("/payment/complete?token=" + token)).build();
}등록 완료보다 호환성 확보가 먼저였다
결제 등록 흐름은 서버 로직만 맞춘다고 끝나지 않았다. 실제 사용자 환경에서 redirect가 어떻게 해석되는지까지 포함해 설계해야 했고, 그래서 이 단계에서는 “정답처럼 보이는 구현”보다 “가장 넓게 동작하는 구현”이 더 중요했다.
다음 글에서는 이 등록 경험이 실제 채널 확장으로 이어지며 SNKRS App에서 KakaoPay를 어떻게 활성화했는지 본다.