ตรวจสอบการแจ้งเตือนจากเซิร์ฟเวอร์ App Store ก่อนดำเนินการกับการคืนเงิน เพราะใครก็ส่งคำขอมายัง URL webhook ของคุณได้
การแจ้งเตือนการคืนเงินเป็นเพียงคำขอ HTTPS ธรรมดาจนกว่าคุณจะตรวจสอบลายเซ็นของมัน บทความนี้อธิบายวิธีตรวจสอบการแจ้งเตือนจากเซิร์ฟเวอร์ App Store และข้อความ RTDN ของ Google Play แต่ละสโตร์ลงลายเซ็นอะไรบ้าง และการคืนเงินที่ถูกปลอมแปลงหรือถูกปฏิเสธโดยผิดพลาดทำให้คุณเสียเงินเท่าใด

ประเด็นสำคัญ
- Apple ส่งการแจ้งเตือนจากเซิร์ฟเวอร์ App Store เวอร์ชัน 2 ทุกรายการในรูปแบบ signedPayload ซึ่งเป็น JSON Web Signature ที่มีสามส่วนแบบ Base64URL ได้แก่ header, payload และลายเซ็น
- header ของ JWS ในการแจ้งเตือนจากเซิร์ฟเวอร์ App Store มีห่วงโซ่ใบรับรอง x5c ที่ประกอบด้วยใบรับรองสามใบ ได้แก่ ใบรับรองปลายทาง (leaf) ที่ใช้ลงนาม ใบรับรองตัวกลางของ Apple ที่มีส่วนขยาย Apple Worldwide Developer Relations 1.2.840.113635.100.6.2.1 และใบรับรองรากของ Apple
- App Store Server Library ของ Apple ตรวจสอบการแจ้งเตือนด้วย SignedDataVerifier ซึ่งตรวจห่วงโซ่ใบรับรอง bundle ID, environment และ appAppleId ในสภาพแวดล้อม production และสามารถตรวจสอบการเพิกถอนใบรับรองแบบออนไลน์ได้
- Real-time developer notifications ของ Google Play ไม่ได้ถูกลงนามโดย Google Play สำหรับ push subscription นั้น Pub/Sub สามารถแนบโทเค็น OpenID Connect ที่ลงนามแล้วไว้ใน header Authorization และเซิร์ฟเวอร์ของคุณจะตรวจสอบลายเซ็น audience และ claim อีเมลของโทเค็นนั้น
- Google ระบุว่า RTDN บอกคุณเพียงว่าสถานะการซื้อเปลี่ยนไป ดังนั้นวิธีตอบสนองที่ปลอดภัยต่อการแจ้งเตือนการคืนเงินใดๆ คือยืนยันกับ Google Play Developer API ก่อนเพิกถอนสิทธิ์การเข้าถึง
- Apple ส่งการแจ้งเตือนจากเซิร์ฟเวอร์ App Store มาจาก subnet 17.0.0.0/8 แต่รายการอนุญาต IP ทำได้เพียงจำกัดว่าใครเข้าถึง endpoint ของคุณได้ ลายเซ็นต่างหากที่พิสูจน์ว่าข้อความมาจาก Apple
- ตัวตรวจสอบที่ปฏิเสธข้อความจริงโดยผิดพลาดสร้างความเสียหายพอๆ กับตัวตรวจสอบที่ยอมรับข้อความปลอม เพราะ CONSUMPTION_REQUEST ที่คุณทิ้งไปมีกรอบเวลาตอบกลับ 12 ชั่วโมง และการพิจารณา chargeback ของ Google Play มีกรอบเวลา 24 ชั่วโมง
ใครก็ตามที่รู้ URL webhook ของคุณสามารถส่งการคืนเงินไปที่นั่นได้ นี่คือข้อเท็จจริงที่น่าอึดอัดเบื้องหลังการแจ้งเตือนจากเซิร์ฟเวอร์ App Store และ Real-time developer notification ของ Google Play ทุกรายการ จนกว่าคุณจะตรวจสอบ ข้อความนั้นเป็นเพียง HTTPS POST ที่มี JSON อยู่ข้างใน หากเซิร์ฟเวอร์ของคุณยกเลิกการสมัครสมาชิก เรียกคืนเครดิต หรือแบนบัญชีเพียงเพราะคำขอระบุว่า REFUND คุณก็ได้มอบอำนาจนั้นให้กับใครก็ได้ที่เขียนคำสั่ง curl เป็น
ทั้งสองสโตร์มีวิธีให้คุณตรวจสอบ Apple ลงนามในตัวการแจ้งเตือนเอง คุณจึงตรวจสอบการแจ้งเตือนจากเซิร์ฟเวอร์ App Store ได้โดยใช้เพียงใบรับรองรากของ Apple ส่วน Google Play ใช้แนวทางที่ต่างออกไป ตัวข้อความไม่ได้ถูกลงนาม แต่การส่งผ่าน Pub/Sub สามารถลงนามได้ และ Google ก็ยังกำหนดให้คุณยืนยันทุกการเปลี่ยนแปลงผ่าน API ของตนอยู่ดี ต่อไปนี้คือสิ่งที่แต่ละฝั่งพิสูจน์ได้ และความเสียหายที่เกิดขึ้นเมื่อคุณผิดพลาดไปทางใดทางหนึ่ง
แต่ละสโตร์ลงนามอะไรจริงๆ
| คำถาม | App Store Server Notifications V2 | Google Play RTDN |
|---|---|---|
| ตัวข้อความถูกลงนามหรือไม่ | ใช่ signedPayload เป็น JWS | ไม่ ฟิลด์ data เป็น JSON ที่เข้ารหัส base64 |
| สิ่งที่พิสูจน์ตัวผู้ส่ง | ห่วงโซ่ใบรับรอง x5c ไปยังใบรับรองรากของ Apple | โทเค็น OIDC ของ Pub/Sub เมื่อใช้ push หรือการ pull ที่ยืนยันตัวตนโดยคุณเอง |
| ออบเจ็กต์ที่ลงนามซ้อนอยู่ภายใน | signedTransactionInfo และ signedRenewalInfo ซึ่งแต่ละตัวเป็น JWS แยกกัน | ไม่มี |
| ฟิลด์ที่ต้องตรงกับแอปของคุณ | bundleId, appAppleId, environment | packageName |
| เครือข่ายต้นทาง | 17.0.0.0/8 ทั้ง sandbox และ production | Google Cloud Pub/Sub |
| ยืนยันก่อนดำเนินการ | ไม่บังคับ ข้อมูลที่ลงนามแล้วคือบันทึกหลักฐาน | บังคับ Google กำหนดให้เรียก Developer API |
| การรับส่งข้อมูล | HTTPS, TLS 1.2 ขึ้นไป | HTTPS push หรือ pull ผ่าน client library |
วิธีตรวจสอบการแจ้งเตือนจากเซิร์ฟเวอร์ App Store
เอกสารของ Apple อธิบายว่า signedPayload เป็นสตริงที่ประกอบด้วยสามส่วนซึ่งเข้ารหัสแบบ Base64URL และคั่นด้วยจุด ได้แก่ header, payload และลายเซ็น โดยลงนามตามข้อกำหนด JSON Web Signature การถอดรหัส payload นั้นง่าย แต่การจะเชื่อถือมันได้ต้องผ่านการตรวจสอบสี่ขั้น
- อ่านอาร์เรย์ x5c จาก header ที่ถอดรหัสแล้ว Apple ระบุลำดับไว้ชัดเจน คือใบรับรองที่มีคีย์สำหรับลงนามมาก่อน ตามด้วยใบรับรองตัวกลางของ Apple แล้วจึงเป็นใบรับรองรากของ Apple
- ตรวจสอบห่วงโซ่นั้นกับใบรับรองรากของ Apple ที่คุณดาวน์โหลดเองจากเว็บไซต์ PKI ของ Apple ซึ่งในทางปฏิบัติคือ Apple Root CA - G3 อย่าเชื่อถือใบรับรองรากที่แนบมากับข้อความเด็ดขาด เพราะผู้ปลอมแปลงสามารถใส่ใบรับรองใดก็ได้ไว้ตรงนั้น
- ยืนยันว่าใบรับรองตัวกลางมีส่วนขยาย Apple Worldwide Developer Relations คือ 1.2.840.113635.100.6.2.1 จากนั้นตรวจสอบลายเซ็น JWS ด้วยกุญแจสาธารณะของใบรับรองปลายทาง
- ตรวจข้อมูลที่ถอดรหัสแล้วเทียบกับแอปของคุณ ได้แก่ bundleId, appAppleId ใน production และ environment ลายเซ็น Apple ที่ถูกต้องบนแอปของนักพัฒนารายอื่น หรือบนการซื้อใน sandbox ที่ส่งมายัง endpoint production ของคุณ ก็ยังไม่ใช่การคืนเงินของคุณ
จากนั้นทำซ้ำอีกครั้งกับสิ่งที่อยู่ข้างใน ออบเจ็กต์ data มี signedTransactionInfo และ signedRenewalInfo ซึ่งแต่ละตัวเป็น JWS แยกกันพร้อมห่วงโซ่ x5c ของตัวเอง handler สำหรับ REFUND ที่ตรวจสอบซองชั้นนอกแต่อ่านธุรกรรมโดยไม่ตรวจสอบ ได้ทิ้งช่องโหว่ไว้ตรงกลาง
ใช้ไลบรารีของ Apple แทนการเขียนเอง
Apple เผยแพร่ App Store Server Library สำหรับ Swift, Java, Python และ Node โดย SignedDataVerifier ของไลบรารีนี้รับใบรับรองรากของคุณ แฟล็กสำหรับการตรวจสอบออนไลน์ environment, bundle ID ของคุณ และสำหรับ production ก็รับ appAppleId ของคุณด้วย ซอร์สโค้ดฝั่ง Node แสดงให้เห็นสิ่งที่มันบังคับใช้ ได้แก่ ใบรับรองในห่วงโซ่ต้องมีสามใบพอดี ส่วนขยายของใบรับรองปลายทางของ Apple 1.2.840.113635.100.6.11.1 และส่วนขยายของใบรับรองตัวกลาง วันที่ใช้งานได้ของใบรับรอง และ bundle ID, app ID และ environment ที่ตรงกัน เมื่อเปิดการตรวจสอบออนไลน์ ไลบรารียังสอบถาม OCSP responder ด้วยว่าใบรับรองปลายทางและใบรับรองตัวกลางถูกเพิกถอนหรือไม่

รายการอนุญาต IP เพียงพอหรือไม่
ไม่เพียงพอ Apple ระบุว่าหากเซิร์ฟเวอร์ของคุณจำเป็นต้องใช้รายการอนุญาต คุณควรเพิ่ม subnet 17.0.0.0/8 ซึ่งใช้ได้ทั้ง sandbox และ production วิธีนี้ช่วยลดสัญญาณรบกวน แต่ไม่ได้พิสูจน์ว่าข้อความเป็นของจริง และจะใช้การไม่ได้ทันทีที่เครือข่ายของ Apple เปลี่ยนไปโดยที่คุณไม่ทราบ ให้การตรวจลายเซ็นเป็นด่านคัดกรองที่แท้จริงต่อไป
วิธีตรวจสอบการแจ้งเตือนการคืนเงินของ Google Play
Google Play เผยแพร่ข้อความ RTDN ไปยัง topic ของ Pub/Sub ที่คุณเป็นเจ้าของ และควรมีเพียง service account google-play-developer-notifications@system.gserviceaccount.com เท่านั้นที่ถือบทบาท Pub/Sub Publisher บน topic นั้น จากนั้น subscription ของคุณจะส่งข้อความด้วยวิธีใดวิธีหนึ่งจากสองวิธี และแต่ละวิธีมีหลักฐานของตัวเอง
- Pull subscription: เซิร์ฟเวอร์ของคุณเปิดการเชื่อมต่อที่ยืนยันตัวตนไปยัง Pub/Sub และดึงข้อความมาเอง ไม่มี endpoint สาธารณะ คนแปลกหน้าจึงไม่มีที่ให้ส่งข้อความเข้ามา
- Push subscription ที่เปิดการยืนยันตัวตน: Pub/Sub ลงนาม JSON Web Token และส่งมาใน header Authorization ในรูปแบบโทเค็น Bearer คู่มือของ Google กำหนดให้ตรวจสอบลายเซ็นของโทเค็น และตรวจว่า claim อีเมลและ audience ตรงกับค่าที่คุณตั้งค่าไว้ และโค้ดตัวอย่างของ Google ยังกำหนดให้ email_verified ต้องเป็น true ด้วย โทเค็นอาจมีอายุได้สูงสุดหนึ่งชั่วโมง
- Push subscription ที่ไม่มีการยืนยันตัวตน: อะไรก็ตามที่เข้าถึง URL ได้สามารถส่งข้อความที่มีหน้าตาเหมือน RTDN เข้ามาได้
ทำไม Google จึงต้องการให้คุณยืนยันผ่าน API
แม้แต่ RTDN ที่ยืนยันตัวตนได้อย่างสมบูรณ์ก็ไม่ได้บอกเรื่องราวทั้งหมด คู่มือการตั้งค่าของ Google ระบุไว้ชัดเจนว่าการแจ้งเตือนเหล่านี้บอกคุณเพียงว่าสถานะการซื้อเปลี่ยนไป และคุณต้องเรียก Google Play Developer API เพื่อรับสถานะที่สมบูรณ์ สำหรับ voidedPurchaseNotification หมายความว่าต้องค้นหาการซื้อนั้นใน Voided Purchases API หรือดึงสถานะปัจจุบันของมันก่อนที่จะเพิกถอนสิ่งใด การเรียก API ยืนยันตัวตนด้วย service account ของคุณเอง คำตอบที่ได้จึงเป็นสิ่งที่ผู้ปลอมแปลงไม่สามารถปลอมได้
การคืนเงินที่ถูกปลอมแปลงหรือถูกปฏิเสธทำให้คุณเสียเงินเท่าใด
การแจ้งเตือนการคืนเงินปลอมที่คุณเชื่อถือจะทำให้คุณเสียลูกค้าที่จ่ายเงิน คุณล็อกการเข้าถึงของคนที่คุณยังถือเงินของเขาอยู่ เขาเปิดตั๋วขอความช่วยเหลือ และบางคนจะไปที่สโตร์เพื่อขอการคืนเงินที่ระบบของคุณแสร้งว่าอนุมัติไปแล้ว ส่วนการซื้อหรือการต่ออายุปลอมสร้างความเสียหายในทางกลับกัน คุณส่งมอบพลังประมวลผล GPU การเรียก API ของบุคคลที่สามที่มีค่าใช้จ่าย พื้นที่จัดเก็บ และเงินจ่ายให้ครีเอเตอร์ โดยอิงกับรายได้ที่ไม่เคยมีอยู่จริง
ความล้มเหลวในทางตรงข้ามนั้นเงียบกว่าและมักใหญ่กว่า ตัวตรวจสอบที่ปฏิเสธข้อความจริง เพราะ appAppleId ผิด การตั้งค่า sandbox ใน production หรือใบรับรองรากที่ล้าสมัย จะทำให้สองกระบวนการคืนเงินที่หลักฐานของคุณมีน้ำหนักหลุดหายไป Apple ขอข้อมูลการใช้งานภายใน 12 ชั่วโมงหลังจาก CONSUMPTION_REQUEST ส่วน Google ให้เวลาคุณ 24 ชั่วโมงในการตอบการพิจารณา chargeback ผ่าน orders.reviewrefund และสำหรับคำสั่งซื้อที่เกิดขึ้นหลังวันที่ 3 สิงหาคม 2026 การแพ้ chargeback ของ Google Play จะทำให้คุณเสียราคาซื้อหักค่าบริการของ Play บวกค่าธรรมเนียมที่ธนาคารของผู้ซื้อเรียกเก็บ
| ความล้มเหลว | สิ่งที่เกิดขึ้น | ความเสียหาย |
|---|---|---|
| ยอมรับ REFUND ปลอม | ลูกค้าที่จ่ายเงินสูญเสียสิทธิ์การเข้าถึง | เวลาของทีมซัพพอร์ตและคำขอคืนเงินจริงที่คุณเป็นฝ่ายชักนำมาเอง |
| ยอมรับการซื้อปลอม | เข้าถึงฟีเจอร์แบบชำระเงินได้ฟรี | พลังประมวลผล การเรียก API พื้นที่จัดเก็บ และเงินที่จ่ายออกไปโดยไม่มีรายได้ |
| ปฏิเสธ CONSUMPTION_REQUEST จริง | Apple ตัดสินโดยไม่มีข้อมูลของคุณ | ยอดขายนั้น บวกทุกอย่างที่ใช้ไปแล้วในการส่งมอบ |
| ปฏิเสธการพิจารณา chargeback จริง | Google ตัดสินโดยไม่มีหลักฐานของคุณ | ราคาซื้อและค่าธรรมเนียมธนาคารสำหรับคำสั่งซื้อหลังวันที่ 3 สิงหาคม 2026 |
อย่าปล่อยให้บั๊กในการตรวจสอบกินเวลาจนเลยกำหนด
- จัดเก็บคำขอดิบก่อนตรวจสอบ เพื่อให้ข้อความที่ถูกปฏิเสธไม่สูญหายไปจริงๆ
- ตั้งการแจ้งเตือนสำหรับการตรวจสอบที่ล้มเหลวทุกครั้ง ข้อความปลอมหนึ่งรายการเป็นเพียงสัญญาณรบกวน แต่หากทุกข้อความล้มเหลวพร้อมกัน นั่นคือบั๊กของคุณเอง
- ฝั่ง Apple คุณสามารถเล่นซ้ำสิ่งที่ปฏิเสธไปได้ Get Notification History ส่งคืน signedPayload ต้นฉบับย้อนหลัง 180 วัน ดังนั้นให้แก้ไขตัวตรวจสอบแล้วนำ payload ที่จัดเก็บไว้หรือกู้คืนมาผ่านตัวตรวจสอบอีกครั้ง
- ฝั่ง Google การพิจารณา chargeback ที่รอดำเนินการมีอยู่เฉพาะในการแจ้งเตือนเท่านั้น audience ของ push ที่ตั้งค่าผิดจึงหมายถึงการพิจารณาที่พลาดไป ให้ทดสอบด้วย Send Test Message ใน Play Console ทุกครั้งที่มีการเปลี่ยนแปลง
RefundHalt ตรวจสอบการแจ้งเตือนจากทั้งสองสโตร์ ยืนยันการคืนเงินกับ API ของสโตร์ก่อนที่จะมีสิ่งใดเปลี่ยนแปลง และตอบ CONSUMPTION_REQUEST รวมถึงการพิจารณา chargeback ภายในกรอบเวลา เพื่อไม่ให้ข้อความปลอมทำให้คุณเสียลูกค้า และไม่ให้ข้อความจริงหลุดรอดไป
คำถามที่พบบ่อย
- ฉันจะตรวจสอบการแจ้งเตือนจากเซิร์ฟเวอร์ App Store ได้อย่างไร
- ตรวจสอบ signedPayload ในฐานะ JSON Web Signature อ่านห่วงโซ่ใบรับรอง x5c จาก header ตรวจสอบห่วงโซ่นั้นกับใบรับรองรากของ Apple ที่คุณดาวน์โหลดจากเว็บไซต์ PKI ของ Apple ตรวจสอบลายเซ็นด้วยกุญแจของใบรับรองปลายทาง แล้วตรวจว่า bundleId, appAppleId และ environment ตรงกับแอปของคุณ App Store Server Library ของ Apple ทำทั้งหมดนี้ใน SignedDataVerifier
- ฉันต้องใช้คีย์ใดในการตรวจสอบการแจ้งเตือนจากเซิร์ฟเวอร์ App Store
- คุณไม่จำเป็นต้องใช้คีย์จาก App Store Connect กุญแจสาธารณะอยู่ในใบรับรองใบแรกของห่วงโซ่ x5c ภายใน header ของ JWS และคุณจะเชื่อถือกุญแจนั้นได้ก็ต่อเมื่อตรวจสอบห่วงโซ่ไปจนถึงใบรับรองรากของ Apple ที่คุณได้มาเองแล้วเท่านั้น คีย์ In-App Purchase ของคุณใช้สำหรับเรียก API ของ Apple ไม่ใช่สำหรับตรวจสอบการแจ้งเตือน
- Real-time developer notifications ของ Google Play ถูกลงนามหรือไม่
- ไม่ Google Play ไม่ได้ลงนามในตัวข้อความ RTDN สำหรับ push subscription ที่เปิดการยืนยันตัวตน Pub/Sub จะส่งโทเค็น OpenID Connect ที่ลงนามแล้วมาใน header Authorization ให้คุณตรวจสอบ และ Google กำหนดให้เรียก Google Play Developer API หลังการแจ้งเตือนทุกครั้งเพื่อรับสถานะที่สมบูรณ์ของการซื้อ
- เซิร์ฟเวอร์ของฉันควรส่งคืนข้อผิดพลาดเมื่อการแจ้งเตือนไม่ผ่านการตรวจสอบหรือไม่
- จัดเก็บคำขอดิบไว้ก่อน แล้วจึงตัดสินใจ Apple จะส่งการแจ้งเตือนเวอร์ชัน 2 ซ้ำหลังได้รับการตอบกลับ 40x หรือ 50x ที่ 1, 12, 24, 48 และ 72 ชั่วโมงหลังความพยายามครั้งก่อน หากความล้มเหลวมาจากการตั้งค่าของคุณเอง Get Notification History ช่วยให้คุณกู้คืน signedPayload ต้นฉบับได้ภายใน 180 วัน และตรวจสอบอีกครั้งหลังแก้ไขแล้ว
- ฉันยังต้องตรวจสอบการแจ้งเตือนอยู่หรือไม่ หากอนุญาตเฉพาะช่วง IP ของ Apple
- ใช่ Apple ขอให้เซิร์ฟเวอร์ที่ใช้รายการอนุญาตยอมรับ subnet 17.0.0.0/8 ซึ่งจำกัดว่าใครเชื่อมต่อได้ แต่ไม่ได้พิสูจน์ว่าข้อความเป็นของจริง ลายเซ็น JWS คือการตรวจสอบที่ผูกการแจ้งเตือนเข้ากับ Apple และกับแอปของคุณ
แหล่งข้อมูลและเนื้อหาเพิ่มเติม
- Apple Developer: signedPayload
- Apple Developer: JWSDecodedHeader
- Apple Developer: Enabling App Store Server Notifications
- Apple Developer: Responding to App Store Server Notifications
- Apple on GitHub: App Store Server Library (Node)
- Android Developers: Get ready (Real-time developer notifications setup)
- Google Cloud: Authentication for push subscriptions
- Play Console Help: Updates to refund protection and chargeback cost responsibility
RefundHalt
ระบบอัตโนมัติสำหรับการคืนเงินบน App Store และ Google Play
อ่านต่อ
คำขอคืนเงินที่มากเกินไปอาจทำให้คุณเสียมากกว่ายอดคืนเงิน เพราะทั้งสองสโตร์มองว่าเป็นสัญญาณด้านคุณภาพ
แนวทาง 5.6.4 ของ Apple ระบุว่าคำขอคืนเงินที่มากเกินไปเป็นสัญญาณว่าแอปไม่ได้มาตรฐานคุณภาพ และข้อตกลงการจัดจำหน่ายสำหรับนักพัฒนาของ Google ก็เปิดให้ถอดแอปของนักพัฒนารายใหม่ได้จากอัตราการคืนเงิน บทความนี้สรุปว่าแต่ละสโตร์เขียนไว้อย่างไร การถูกถอดแอปมีต้นทุนเท่าไร และจะอยู่ให้ห่างจากความเสี่ยงได้อย่างไร
การพลาดการแจ้งเตือนการคืนเงินอาจทำให้คุณเสียการคืนเงินเพียงกรณีเดียวที่คุณมีสิทธิ์โต้แย้ง และทั้งสองสโตร์ให้คุณกู้คืนได้หากตรวจสอบทันเวลา
เมื่อเซิร์ฟเวอร์ของคุณพลาดการแจ้งเตือนการคืนเงิน กระบวนการคืนเงินสองแบบที่ขอหลักฐานจากคุณจะปิดลงโดยไม่มีคุณ บทความนี้อธิบายว่า Apple และ Google Play ส่งซ้ำอย่างไร คุณกู้คืนการแจ้งเตือนการคืนเงินที่พลาดไปได้ย้อนหลังนานแค่ไหน และช่องโหว่ในการกู้คืนอยู่ตรงไหน