웹훅 연동하기

웹훅 연동을 통해 결제 결과를 안전하게 처리하실 수 있습니다.

아임포트 웹훅(webhook)을 사용하여 아임포트 서버에 저장된 결제 정보를 가맹점 서버에 동기화하고 네트워크 불안정성을 보완하는 방법을 설명합니다.

웹훅(Webhook)이란?

특정 이벤트가 발생하였을 때 타 서비스나 응용프로그램으로 알림을 보내는 기능입니다. Webhook 프로바이더는 해당 이벤트가 발행하면 HTTP POST 요청을 생성하여 callback URL(endpoint)로 이벤트 정보을 보냅니다. 주기적으로 데이터를 폴링(polling)하지 않고 원하는 이벤트에 대한 정보만 수신할 수 있어서 webhook은 리소스나 통신측면에서 훨씬 더 효율적입니다. Webhook을 활용하면 커스텀기능이나 다른 애플리케이션과 연동하여 기능을 확장할 수 있습니다.

웹훅(Webhook) 연동은 필수 인가요?

아임포트 서버에서 클라이언트 응답을 전달할 때 Wi-Fi 연결 끊김, 혹은 브라우저 자동 리로드 등의 이유로 클라이언트에서 결제 완료에 대한 응답을 받지 못하는 경우가 간헐적으로 발생합니다. 이런 경우를 대비해서 아임포트 서버에서 가맹점 서버로 webhook 이벤트를 발송하여 결제 정보를 동기화할 수 있도록 합니다.

아임포트 웹훅(webhook)은 다음과 같은 경우에 호출됩니다.

  • 결제(예약 결제 포함)가 승인되었을 때(모든 결제 수단) - (status : paid)
  • 가상계좌가 발급되었을 때 - (status : virtual_account_issued)
  • 가상계좌에 결제 금액이 입금되었을 때 - (status : paid)
  • 관리자 콘솔에서 결제 취소되었을 때 - (status : cancelled)
  • 결제(예약 결제 포함)가 실패했을 때(status: failed)

웹훅(Webhook)수신을 위한 URL 설정은 두가지 형태로 지원됩니다.

관리자콘솔 설정

;

아임포트 webhook이 호출될 때 결제 정보를 통보받을 URL을 설정하려면 아임포트 관리자 콘솔 내 결제연동->실연동관 탭을 선택합니다. Endpoint URL 항목에 웹훅으로 전송될 데이타를 수신할 URL주소를 기재합니다.

Content-Typeapplication/json 또는 application/x-www-form-urlencoded으로 지정할 수 있습니다. 또 호출 테스트 버튼을 누르면 해당 URL을 호출하여 테스트할 수 있습니다.

파라미터 설정

function requestPay() {
    // IMP.request_pay(param, callback) 결제창 호출
    PORT.requestPayment({
        ...            // 중략
        noticeUrls : ['https://웹훅수신 URL'],   //웹훅수신 URL 설정
        ...            // 중략
    }, function (rsp) { // callback
        if (rsp.isSuccess) {
            console.log(rsp);
        } else {
            console.log(rsp);
        }
    });
}

웹훅(Webhook) 요청 검증하기

웹훅 수신주소는 공개된 URL이기 때문에 요청자 클라이언트(client IP)가 아임포트(Iamport IP)인지 반드시 확인해야 합니다. 아임포트 웹훅은 다음의 두 가지 고정된 IP 로 부터 요청이 생성됩니다.

**52.78.100.19
52.78.48.223
52.78.5.241 (웹훅 테스트 발송 버튼으로  전송되는 경우)**

Webhook POST 요청의 본문은 다음의 정보를 포함합니다. 가맹점 서버는 해당 정보를 수신하고 아임포트 서버에서 결제 정보를 조회하여 검증 및 저장할 수 있습니다.

  • tx_id: 결제번호
  • payment_id: 주문번호
  • status: 결제 상태

웹훅 EndPoint URL 수신부 POST 요청에 대한 코드 예시

Webhook 이벤트의 POST 요청을 처리할 endpoint를 다음과 같이 생성하여 결제 정보를 조회하고 검증하여 저장합니다.

app.use(bodyParser.json());
...
// "/iamport-webhook"에 대한 POST 요청을 처리
app.post("/iamport-webhook", async (req, res) => {
  try {
      // req의 body에서 tx_id, payment_id 추출
      const { tx_id, payment_id } = req.body;
      // 액세스 토큰(access token) 발급 받기
      /* ...중략... */
      // payment_id로 아임포트 서버에서 결제 정보 조회
      /* ...중략... */
      const paymentData = getPaymentData.data.response; // 조회한 결제 정보
      const transaction = paymentData.transactions.filter(transaction => paymentData.primary_tx_id === tx_id)[0]

      ...
      // DB에서 결제되어야 하는 금액 조회
      const order = await Orders.findById(transaction.payment_id);
      const amountToBePaid = order.amount; // 결제 되어야 하는 금액
      ...
      // 결제 검증하기
      const { totalAmount, status } = transaction;
      // 결제금액 일치. 결제 된 금액 === 결제 되어야 하는 금액
      if (amount === amountToBePaid) {
        // DB에 결제 정보 저장
        await Orders.findByIdAndUpdate(payment_id, { $set: paymentData });
        switch (status) {
          case "VIRTUAL_ACCOUNT_ISSUED": // 가상계좌 발급
            // DB에 가상계좌 발급 정보 저장
            const { virtual_account } = tranasction.payment_method_detail;

            await Users.findByIdAndUpdate("/* 고객 id */", { $set: virtual_account });

            // 가상계좌 발급 안내 문자메시지 발송
            SMS.send({ text: `가상계좌 발급이 성공되었습니다. 계좌 정보: ${virtual_account}`});
            res.send({ status: "virtualAccountIssued", message: "가상계좌 발급 성공" });
            break;
          case "PAID": // 결제 완료
            res.send({ status: "success", message: "일반 결제 성공" });
            break;
        }
      } else { // 결제금액 불일치. 위/변조 된 결제
        throw { status: "forgery", message: "위조된 결제시도" };
      }
  } catch (e) {
    res.status(400).send(e);
  }
});

서버가 결제 정보를 수신 하는 순서는 보장되지 않습니다

기본적으로 아임포트 서버에서 webhook이 호출되면 가맹점 응답을 기다리지 않고 클라이언트에 302 redirect 응답을 보내기 때문에 결과 도달에 대한 순서를 보장하지 않습니다. 다만 가맹점 요청이 있을 경우 webhook 호출 이후에 클라이언트에 302 redirect 또는 callback 응답을 보내어 순서를 보장 해드리고 있습니다. 웹훅 우선순위 요청은 support@iamport.kr 로 가맹점 식별코드를 기재하여 요청해 주시면 됩니다.

웹훅 재전송 정책

(TODO: 웹훅 재전송 여부를 선택하는 방식 결정 필요)

웹훅 재전송에는 exponential backoff를 적용하고 있습니다. 최대 5회까지 재전송되며, 1 → 2 → 4 → 8 → 16 초 간격으로 웹훅 발송을 재시도합니다.

backoff를 적용하면 갑작스럽게 가맹점 서버로 향하는 트래픽이 증가하는 것을 어느정도 막을 수 있지만 결제 요청이 한 번에 몰리게 된다면 웹훅 재시도 또한 같은 시간 간격으로 이루어질 것이기 때문에 트래픽을 효과적으로 분산하는 데에 한계가 있습니다.

때문에 아임포트의 웹훅은 backoff와 더불어 jittering을 적용하고 있습니다. jittering이란 랜덤하게 지연 시간을 변형시키는 방법으로, backoff만을 적용했을 때보다 더 균형있게 트래픽을 분산시킬 수 있습니다.