판단의 출발점
결제를 마친 직후 “장바구니에 상품이 남아 있어요”라는 알림이 옵니다. 고객은 주문이 실패했는지 의심하고 CRM 담당자는 구매자를 제외했는데 왜 발송됐는지 확인합니다. 아래에서 다루는 상황은 오발송 구조를 설명하기 위한 설계 예시입니다.
가장 먼저 구분할 것은 여정에 들어갈 자격과 지금 메시지를 받을 자격입니다. 장바구니를 만들었을 때 미구매였다는 사실은 대기 시간이 끝난 뒤에도 미구매라는 뜻이 아닙니다. 구매 이벤트가 늦게 반영됐거나, 구매와 장바구니가 서로 다른 사용자·업무에 연결됐거나, 같은 사건이 여정을 다시 시작시켰는지 나눠 확인해야 합니다. OneSignal은 이벤트 기반 여정의 동시 진입을 허용하며 Shopify 웹훅은 발생 순서와 다르게 도착할 수 있습니다. ‘구매자를 제외했다’는 설정 이름만으로 이 조건들이 해결되지는 않습니다.[1][5]
해결의 출발점은 구매 완료의 정의, 어느 장바구니 안내를 끝낼지, 언제 상태를 다시 확인할지 정하는 것입니다. 그다음 플랫폼의 종료·분기 기능과 애플리케이션의 발송 판단을 연결합니다. 다만 발송 직전 조회를 추가해도 외부 주문 시스템과 메시징 시스템이 하나의 거래처럼 동시에 확정되는 것은 아닙니다. 어디까지 차단할 수 있고 어디부터는 이미 전송됐을 수 있는지 경계를 정해야 합니다.
‘구매 완료’와 ‘안내를 멈출 때’는 같지 않을 수 있다
Shopify의 Classic Webhooks를 예로 들면 orders/create는 주문 생성, orders/paid는 주문이 결제된 사건입니다. 현재 주문의 결제 상태에도 PENDING, AUTHORIZED, PAID 등이 따로 있습니다. PAID에는 자동·수동 결제 매입뿐 아니라 관리자가 결제 완료로 표시한 경우도 포함됩니다. 따라서 결제 버튼 클릭이나 주문 생성만을 실제 자금 처리 완료의 증거로 쓰면 안 됩니다.[3][4]
그러나 장바구니 안내는 결제 완료까지 계속 보내야 하는 메시지가 아닙니다. 주문이 접수돼 결제가 진행 중인 사람에게도 “아직 구매하지 않았다”는 안내는 부적절할 수 있습니다.
이 글의 설계 예시는 로그인 고객의 온라인 선결제 주문을 대상으로 다음 정책을 선택합니다. 실제 서비스의 결제 방식과 고객 응대 정책에 맞춰 조정할 기준이지, Shopify나 OneSignal의 기본 동작이 아닙니다.
- 해당 장바구니에서 주문이 생성되거나 결제가 진행 중이면 안내를 보류합니다. 구매 완료가 확인되면 그 장바구니의 기존 안내를 종료합니다.
- 주문·고객·장바구니의 연결이 불명확하거나 현재 상태를 조회하지 못하면 미구매로 간주하지 않고 보류합니다.
- 취소·부분 환불·전체 환불이 발생해도 종료한 장바구니 안내를 되살리지 않습니다. 새 장바구니나 재구매 캠페인은 별도의 조건으로 판단합니다.
Shopify는 orders/cancelled와 refunds/create도 구분합니다. 특히 refunds/create는 환불 레코드 생성에 관한 사건이며 자금 이동 자체와 독립적입니다. 이 이벤트를 ‘구매하지 않은 상태로 복귀했다’거나 ‘다시 광고를 받아도 된다’는 신호로 해석할 근거는 없습니다.[3]
판정 단위도 적어야 합니다. 고객 U가 장바구니 C1을 구매했다고 해서 새 장바구니 C2까지 완료한 것은 아닙니다. 고객 단위 purchased=true/false 하나 대신, 서비스가 관리하는 고객–장바구니–주문 연결 관계로 어느 안내가 끝났는지 판단합니다. 연결할 수 없다면 추측해서 보내기보다 해당 고객의 안내를 보류하는 쪽으로 정책을 정합니다.
고객 행동에 맞춰 이어지는 메시지 — 고객 단계와 행동을 조건으로 삼아 채널과 대기 시간을 연결하고 실험 결과로 여정을 다듬습니다.
구매 시각과 CRM 반영 시각 사이에 발송이 끼어든다
다음은 장바구니 C1의 안내를 10시 30분에 판단하도록 예약한 가상 타임라인입니다. 실제 지연 측정치나 특정 제품의 처리 속도 보장이 아닙니다.
| 시각 | 발생한 일 | 판단에 남겨야 할 구분 |
|---|---|---|
| 10:00:00 | C1의 리마인드 후보를 만들고 대기 시작 | 여정 진입 사유와 장바구니 식별자 |
| 10:29:58 | 주문 원천에 C1의 구매 완료가 기록됨 | 구매 사건 발생 시각 |
| 10:30:00 | 발송 작업이 실행되지만 CRM에는 구매가 아직 반영되지 않음 | 발송 판단 시각과 읽은 상태의 버전 |
| 10:30:01 | 오래된 상태로 허용한 요청을 메시징 제공자가 접수하고 전송을 시작 | 요청 접수·전송 단계의 시각과 메시지 ID |
| 10:30:02 | 구매 웹훅이 애플리케이션에 도착 | 웹훅 수신 시각 |
| 10:30:03 | 애플리케이션이 C1을 발송 제외로 저장 | 내부 자격 상태 반영 시각 |
| 10:30:05 | 메시징 플랫폼에 종료용 상태·이벤트가 반영됨 | 플랫폼 반영과 실제 여정 종료의 확인 신호 |
마지막 줄에서 여정이 끝나더라도 앞서 전송된 메시지가 없었던 일이 되지는 않습니다. OneSignal의 Cancel message API 역시 예약·전송 중 메시지를 중단하는 기능이며 이미 전송 중이면 모든 수신을 막지 못할 수 있다고 명시합니다.[10]
이 타임라인에서 발송 작업이 10시 30분에 주문 원천을 다시 읽어 구매 완료를 확인했다면 요청을 만들지 않는 설계가 가능합니다. 반대로 원천 조회 후 메시징 제공자에게 넘기기 직전에 구매가 발생하면 작은 경쟁 구간이 남습니다. 조회를 한 번 넣었다는 이유로 ‘구매 후 오발송 0건’을 보장할 수는 없습니다.
조사할 때는 구매 발생, 웹훅 수신, 내부 반영, 플랫폼 반영을 한 시각으로 합치지 않습니다. 원천 시각의 의미와 서버 간 시계 차이를 확인하고 제공되지 않는 시각은 미확인으로 남깁니다. 위 표의 필드가 모든 플랫폼 로그에서 자동 제공되는 것도 아닙니다. 운영자가 확보한 로그와 추가 계측을 구분해야 합니다.
OneSignal에서 설정할 것과 별도로 판단할 것
2026년 9월 26일 확인한 공식 문서 기준으로 다음 기능을 제공합니다. 기능 이름보다 그 기능이 읽는 데이터와 적용 위치를 확인하는 것이 중요합니다.
| 필요한 제어 | 공식 기능과 적용 범위 | 애플리케이션·운영 정책에 남는 일 |
|---|---|---|
| 진입·종료·재진입 | 세그먼트 또는 Custom Event로 진입합니다. 일반 Re-entry rules는 세그먼트 기반 여정에 적용되며 이벤트 기반 여정은 재진입을 허용합니다.[1] | 같은 사건의 중복 진입과 새 장바구니의 정당한 진입을 구분합니다. |
| 대기 뒤 다시 분기 | Wait는 시간을 지연합니다. Wait Until은 조건을 기다리고, Yes/No branch는 세그먼트 소속이나 메시지 행동에 따라 나눕니다.[2] | 시간이 지났다는 사실을 미구매 확인으로 대체하지 않습니다. 만료 후 어디로 보낼지도 정합니다. |
| 해당 업무의 완료 연결 | 이벤트 기반 진입의 Wait Until에는 시작 이벤트와 대기 이벤트의 속성값을 연결하는 Event Matching이 있습니다.[2] | C1 완료가 C2의 안내를 끝내지 않도록 값을 연결합니다. 이 기능을 모든 Exit rule의 건별 매칭 보장으로 확대하지 않습니다. |
| 다른 여정과의 충돌 | 태그·제외 세그먼트로 여정 참여를 조정합니다.[1][2] | 태그 변경을 여러 여정이 동시에 확인하는 상황까지 원자적 잠금으로 보장한다고 가정하지 않습니다. |
출처: OneSignal Journey settings·Journey actions.[1][2]
특히 이미 구매한 사람이 진입 조건과 종료 조건을 동시에 만족하는 경우를 시험해야 합니다. OneSignal 문서는 첫 단계를 수행한 뒤 종료될 수 있으므로 시작 단계에 대기를 넣거나, 세그먼트 진입에서는 제외 대상을 명시하도록 안내합니다. 이벤트 진입과 세그먼트 진입을 같은 진입 규칙에 동시에 조합할 수 있다고 생각해서도 안 됩니다.[1]
공식 장바구니 튜토리얼은 cart_updated를 진입과 종료에 함께 사용해 최신 장바구니 내용으로 다시 시작하는 패턴을 제시합니다. 이때도 재진입 필터와 빈 장바구니 처리가 맞아야 합니다. 구매 뒤 오래된 장바구니 업데이트가 다시 들어오는 문제까지 이 패턴 하나로 해결되는 것은 아닙니다.[12]
엄격한 제외가 필요하면 발송 요청을 만드는 곳에서 판단한다
기존 여정의 구매 종료 조건, 시작 단계, 식별자 연결이 잘못된 문제라면 먼저 그 설정을 고치면 됩니다. 모든 서비스에 별도 발송 시스템이 필요한 것은 아닙니다.
여러 주문 시스템을 거치거나 이벤트 지연이 반복되고 잘못된 안내를 줄이기 위해 일부 정상 발송을 포기할 수 있다면 애플리케이션이 최종 발송 요청을 소유하는 방식을 검토할 수 있습니다. 다음은 구현 방향을 설명하는 설계안입니다.
발송 후보 → 주문 원천 또는 검증된 최신 상태 확인 → 고객·장바구니 연결 확인 → 동의·구독·중복·빈도 확인 → 발송 허용 기록 → 메시지 API 요청
주문이 진행 중이거나 완료됐으면 제외하고 조회 실패·관계 불명은 보류합니다. 허용 기록에는 판단 시각, 근거 상태, 캠페인 설정 버전, 선택한 채널과 수신 대상을 남깁니다. 큐에서 오래 대기한 작업은 실행할 때 다시 판단합니다. 같은 구간을 애플리케이션으로 옮겼다면 기존 Journey 메시지 단계가 따로 보내지 않도록 발송 주체를 하나로 정합니다.
두 작업이 동시에 “아직 안 보냈다”고 읽는 상황도 시험합니다. 이 설계에서는 고객·장바구니·안내 단계별 발송 권한의 확인과 예약을 한 번에 처리하도록 중복 제약과 트랜잭션을 구성합니다. 재시도는 새 허용 기록을 만드는 대신 기존 기록과 요청 키를 이어 씁니다. 이 제약은 우리 발송 작업끼리의 충돌을 제어하려는 것이며 외부 주문 시스템의 구매까지 잠그는 장치는 아닙니다.
Journey webhook을 앞에 붙였다는 사실만으로 이 구조가 완성되지는 않습니다. 공식 기능은 외부 서버로 HTTP 요청을 보내는 것입니다. 웹훅 응답과 다음 메시지 단계가 주문 원천까지 포함해 원자적으로 묶인다는 보장은 확인되지 않습니다. 또한 Journey webhooks는 별도 이용 조건을 확인해야 합니다. 위와 같은 발송 관문은 자체 작업 큐·스케줄러와 메시지 API로 구성하는 별도 설계이지, 기본 Journey의 자동 보장이 아닙니다.[13]
최종 조회와 외부 전송 사이의 경쟁은 여전히 남습니다. 이를 줄이려면 결제 진행을 먼저 감지해 안내를 보류하고 대기열 지연을 줄이며 상태가 불확실하면 보내지 않는 방법이 있습니다. 하지만 외부 Shopify와 OneSignal을 하나의 트랜잭션으로 묶지 못하는 구조에서 실제 구매 시각 이후의 모든 수신을 막겠다고 약속해서는 안 됩니다. 운영 기준에는 ‘최종 확인에서 구매를 확인한 요청은 생성하지 않는다’와 ‘이미 제공자에게 넘긴 요청은 취소 후에도 수신될 수 있다’를 함께 적습니다.
캠페인에 필요한 데이터부터 연결 — 캠페인에서 역산한 이벤트와 속성을 정의하고 식별자와 전송 시점, 측정 도구의 역할을 맞춥니다.
중복 처리·빈도 제한·사용자 식별은 서로 다른 문제다
Shopify 웹훅은 순서가 뒤바뀌거나 중복될 수 있고, 전달 자체도 항상 보장되지는 않습니다. 공식 문서는 누락을 보완할 주기적 원천 대조를 권고합니다. 따라서 늦게 온 ‘장바구니 존재’ 사건이 이미 확정한 구매 종료를 되돌리지 않도록 상태 전이 규칙을 두고, 누락된 구매는 원천 조회로 보완해야 합니다.[5][6]
수신 중복과 발송 중복도 따로 막습니다. Shopify의 X-Shopify-Webhook-Id는 개별 전달 중복을 확인하는 데 쓰고, X-Shopify-Event-Id는 같은 상점의 행동에서 나온 전달들을 연결하는 데 씁니다. 애플리케이션은 이 기록과 별도로 고객·장바구니·안내 단계에 대한 발송 기록을 관리합니다. 사건 ID만 중복 제거해도 다른 여정이 같은 목적의 메시지를 만드는 문제는 남기 때문입니다.[6]
OneSignal 메시지 API를 재시도할 때는 같은 논리적 요청의 idempotency_key를 유지합니다. 알림 생성 요청의 키 보존 기간은 30일입니다. 반면 Custom Events API의 중복 제거는 4시간 안의 best-effort이므로 영구적·엄격한 사건 중복 제거로 의존하면 안 됩니다. 어느 쪽도 ‘현재 이 고객에게 보내도 되는가’를 대신 판단하지 않습니다.[7][8]
빈도 제한은 과다 발송을 줄이는 기능입니다. OneSignal의 해당 기능은 푸시에 적용되며 이메일·SMS·인앱 메시지의 통합 상한이 아닙니다. 상한에 걸린 푸시는 나중에 보내려고 쌓아 두는 것이 아니라 버립니다. 구매 여부 판정이나 전 채널 캠페인 우선순위와 분리해 설계해야 합니다.[9]
여러 기기의 동일 고객은 서비스 고객 ID를 기준으로 안내를 판단하되, 발송 결정 한 번과 단말 한 곳 수신은 같은 조건이 아닙니다. 단말 하나만 대상으로 삼겠다면 허용된 수신 대상을 고르는 규칙까지 필요합니다. 공용 기기에서는 로그아웃 처리도 확인합니다. OneSignal 모바일 SDK의 logout()은 현재 모바일 구독을 기존 사용자와 분리하지만 다른 구독까지 로그아웃시키지는 않습니다.[11]
로그아웃 전에 이미 넘어간 알림의 노출까지 이 호출로 해결된다고 가정하지 않습니다. 잠금 화면 문구에는 불필요한 구매 정보를 넣지 않고, 알림의 링크를 열었을 때는 현재 로그인 계정의 장바구니 접근 권한을 다시 검사합니다. 채널 구독과 서비스가 관리하는 마케팅 동의도 각각 확인합니다. 수신 거부를 다른 채널로 우회하는 규칙을 자동으로 만들지 않습니다.
정상 발송보다 ‘보내지 않아야 하는 조건’을 먼저 시험한다
다음 매트릭스는 앞서 정의한 가상 서비스의 선택 정책입니다. 플랫폼의 자동 보장이나 실행을 마친 테스트 결과가 아닙니다. 예시 서비스는 고객당 활성 장바구니 안내 한 건, 한 번의 안내 단계, 사전에 선택한 허용 채널·수신 대상을 사용합니다. 새 장바구니도 기존 안내의 종료와 별도로 정한 발송 상한을 확인한 뒤 후보로 만듭니다.
| 예외·입력 조건 | 이 예시에서 기대하는 동작 | 주 책임 | 확인할 신호 |
|---|---|---|---|
| 정상 미구매, 동의·대상 유효 | 최종 확인을 통과한 안내 한 건만 요청 | CRM·발송 개발 | 허용 사유, 장바구니 ID, 발송 기록·메시지 ID |
| 진입 시점에 이미 구매 완료 | 첫 메시지를 만들지 않고 종료·제외 | CRM·QA | 시작 단계와 제외 경로, 요청 미생성 기록 |
| 구매 웹훅이 늦지만 원천 조회에는 구매가 보임 | 최종 발송 관문에서 제외 | 주문 연동·발송 개발 | 조회 결과·조회 시각과 제외 사유 |
| 구매 사건을 두 번 받음 | 같은 구매 상태 전이는 한 번 반영하고 안내를 재생성하지 않음 | 데이터 연동 | 수신 중복 식별자와 업무 처리 기록 |
| 구매 뒤 오래된 장바구니 사건이 도착 | 종료한 C1을 미구매로 되돌리지 않음 | 데이터 연동 | 사건 시각·상태 버전, 역행 거부 기록 |
| 최종 조회 전에 구매 / 조회 후 전송과 경쟁 | 전자는 제외. 후자는 이미 넘겼는지 구분하고 잔여 위험으로 기록 | 발송 개발·운영 | 최종 조회·요청·구매 시각, 취소 시도와 결과 |
| 두 여정이 동시에 같은 안내를 요청 | 애플리케이션의 공통 발송 기록에서 한 건만 허용 | CRM·발송 개발 | 중복 예약 거부와 선택한 캠페인 |
| 같은 고객이 여러 기기·프로필로 존재 | 고객 기준 중복을 막고 선택한 수신 대상만 사용. 연결 불명은 보류 | 식별자 연동 | 고객–프로필–구독 매핑과 대상 선택 기록 |
| 공용 기기에서 A 로그아웃 후 B 로그인 | 이후 A 대상 후보에서 해당 기기를 제외. 기존 알림 링크에서도 A 데이터 접근 차단 | 앱 개발·QA | 사용자 전환, 구독 연결, 링크 권한 검사 결과 |
| 대기 중 수신 거부 | 최종 판단에 반영된 거부는 제외. 이미 넘긴 요청은 별도로 추적 | CRM·앱 개발 | 동의·구독 변경 시각, 제외·잔여 전송 기록 |
| 취소·부분 환불·전체 환불 | 기존 장바구니 안내를 재개하지 않음 | CRM·주문 운영 | 종료 유지, 새 캠페인 진입 승인 여부 |
| 상태 조회 실패·구매 이벤트 누락 | 확인 전 보류하고 원천 대조로 복구. ‘정상 미구매’로 집계하지 않음 | 연동 운영 | 보류 사유·대조 작업·확인되지 않은 건수 |
실제 시험은 소유하거나 허가받은 테스트 상점·계정·기기에서 수행합니다. 큐에서 구매 이벤트를 의도적으로 늦추거나 같은 테스트 사건을 재전송해 정상 경로와 비교합니다. 발송 요청을 만들지 않은 것, 플랫폼이 전송하지 않은 것, 기기에 표시되지 않은 것은 각각 다른 검증 결과입니다.
시나리오별 실제 데이터·관측 결과·재시험 기록은 기존 CRM 캠페인 QA 시나리오·결과 기록집에 남길 수 있습니다. 여정의 진입·종료·재진입 정책을 먼저 합의할 때는 OneSignal 고객 여정 설계 워크북을 함께 사용합니다.
다음 발송 전에 확인할 한 가지
지금 운영 중인 장바구니 안내 하나를 골라 이렇게 물어보자.
“이 고객이 구매했다면, 어느 기록이 어느 시점에 이 발송을 막는가?”
답이 ‘구매자를 제외하는 세그먼트가 있다’에서 끝난다면 그 세그먼트가 언제 갱신되고 어떤 고객·장바구니를 가리키며, 이미 대기 중인 메시지에 어떻게 적용되는지까지 따라가야 합니다. 설정 누락이면 기존 도구로 고치고, 데이터 지연이나 여러 발송 경로의 충돌이면 개발·CRM·QA가 같은 타임라인을 놓고 판단할 일입니다.
구매 후 오발송을 줄이는 데 필요한 것은 여정의 개수보다 명확한 종료 정책, 확인 가능한 상태, 실제로 보내지 않았다는 증거다.
여정 설계와 구현·검증의 책임을 함께 나눠야 한다면 IXC CRM 마케팅 자동화 페이지에서 설명하는 지원 범위를 검토합니다.



