Hello,
I have been trying to submit my app for review but i have been trying for several days to get my hmac review accepted. I dont know what it is happening because i feel my toml and webhook are correct.
Can anyone help me?
This is my toml:
Configuración de la app Reelistic
client_id = “c56e555af76742c061d56”
name = “reelistic-prod”
application_url = “https://www.reelisticapp.com”
embedded = true
[webhooks]
api_version = “2026-01”
[[webhooks.subscriptions]]
compliance_topics = [“customers/data_request”, “customers/redact”, “shop/redact”]
uri = “/api/webhooks/shopify”
[access_scopes]
scopes = “read_customers,read_orders,read_products”
optional_scopes =
use_legacy_install_flow = false
[auth]
redirect_urls = [
“https://prod-api.reelisticapp.com/api/shopify/oauth2callback”
]
This is my webhook code:
from fastapi import APIRouter, Request, Response
import hmac
import hashlib
import base64
import logging
import json
from app.core.secrets_manager import secrets_manager
router = APIRouter()
logger = logging.getLogger(_name_)
logger.setLevel(logging.INFO)
async def get_raw_body(request: Request) → bytes:
"""Get raw request body for HMAC verification"""
try:
return await request.body()
except Exception as e:
logger.error(f"Failed to read request body: {e}")
return b""
async def log_shopify_request(body_bytes: bytes, request: Request):
"""Logs the raw body and headers of Shopify requests"""
try:
try:
body_json = json.loads(body_bytes)
except Exception:
body_json = None
logger.info("=== Shopify Webhook Received ===")
logger.info(f"Path: {request.url.path}")
logger.info(f"Headers: {dict(request.headers)}")
logger.info(f"Raw Body: {body_bytes.decode('utf-8')}")
logger.info(f"Parsed JSON: {body_json}")
logger.info("===============================")
except Exception as e:
logger.error(f"Failed to log Shopify request: {e}")
def verify_webhook(data: bytes, hmac_header: str) → bool:
"""Verify Shopify webhook HMAC signature"""
try:
client_secret = secrets_manager.get("SHOPIFY_SECRET")
if not client_secret:
logger.error("SHOPIFY_SECRET not found in secrets manager")
return False
computed_hmac = hmac.new(
client_secret.encode("utf-8"),
data,
hashlib.sha256
).digest()
computed_hmac_b64 = base64.b64encode(computed_hmac).decode("utf-8")
logger.info(f"Webhook verification - Received HMAC: {hmac_header}")
logger.info(f"Webhook verification - Computed HMAC: {computed_hmac_b64}")
logger.info(f"Webhook verification - Body length: {len(data)} bytes")
logger.info(f"Webhook verification - Match: {hmac.compare_digest(computed_hmac_b64, hmac_header)}")
\# DEBUG: Mostrar secreto del backend (solo pruebas)
logger.info(f"\[DEBUG\] Backend SHOPIFY_SECRET = {client_secret}")
return hmac.compare_digest(computed_hmac_b64, hmac_header)
except Exception as e:
logger.error(f"HMAC verification failed with exception: {e}", exc_info=True)
return False
@router.post(“/customers/data_request”)
async def customers_data_request(request: Request):
"""GDPR: Customer requests their data"""
body = await get_raw_body(request)
hmac_header = request.headers.get("X-Shopify-Hmac-SHA256", "")
\# DEBUG: mostrar HMAC recibido
logger.info(f"\[DEBUG\] Received HMAC header = {hmac_header}")
if not hmac_header:
logger.error("Missing X-Shopify-Hmac-SHA256 header")
return Response(status_code=401)
if not verify_webhook(body, hmac_header):
logger.error("Webhook verification failed for customers/data_request")
return Response(status_code=401)
await log_shopify_request(body, request)
try:
payload = json.loads(body)
logger.info(f"Processing data request for shop: {payload.get('shop_domain')}")
except Exception as e:
logger.error(f"Error processing data request: {e}")
return Response(status_code=200)
@router.post(“/customers/redact”)
async def customers_redact(request: Request):
"""GDPR: Delete customer data"""
body = await get_raw_body(request)
hmac_header = request.headers.get("X-Shopify-Hmac-SHA256", "")
if not hmac_header:
logger.error("Missing X-Shopify-Hmac-SHA256 header")
return Response(status_code=401)
if not verify_webhook(body, hmac_header):
logger.error("Webhook verification failed for customers/redact")
return Response(status_code=401)
await log_shopify_request(body, request)
try:
payload = json.loads(body)
logger.info(f"Processing customer redaction for shop: {payload.get('shop_domain')}")
except Exception as e:
logger.error(f"Error processing customer redaction: {e}")
return Response(status_code=200)
@router.post(“/shop/redact”)
async def shop_redact(request: Request):
"""GDPR: Delete shop data after uninstall"""
body = await get_raw_body(request)
hmac_header = request.headers.get("X-Shopify-Hmac-SHA256", "")
if not hmac_header:
logger.error("Missing X-Shopify-Hmac-SHA256 header")
return Response(status_code=401)
if not verify_webhook(body, hmac_header):
logger.error("Webhook verification failed for shop/redact")
return Response(status_code=401)
await log_shopify_request(body, request)
try:
payload = json.loads(body)
logger.info(f"Processing shop redaction for shop: {payload.get('shop_domain')}")
except Exception as e:
logger.error(f"Error processing shop redaction: {e}")
return Response(status_code=200)