Troubleshooting
Common issues and how to diagnose them. Most problems fall into one of five categories: paywall not showing, buttons not responding, config decode errors, product resolution failures, or filter mismatches.
Paywall Not Showing
The most common issue. A placement is registered but no paywall appears.
Step 1: Run a Dry-Run Trace
The dry-run endpoint simulates the SDK's campaign evaluation logic server-side and returns a full trace showing exactly why a paywall was or was not matched.
curl -X POST https://agentwallie.com/api/v1/projects/PROJECT_ID/dry-run \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_your_private_key" \
-d '{
"placement_name": "feature_gate",
"user_attributes": {
"plan": "free",
"session_count": 5
},
"device_info": {
"platform": "ios",
"os_version": "17.0"
},
"entitlements": [],
"frequency_state": {}
}'Successful match response
{
"matched": true,
"campaign": { "id": "camp_abc", "name": "Onboarding Flow" },
"audience": { "id": "aud_001", "name": "Free Users", "priorityOrder": 0 },
"experiment": { "id": "exp_001", "status": "running" },
"variant": { "id": "var_001", "trafficPercentage": 100 },
"paywall": { "id": "pw_abc123", "name": "Premium Upgrade", "slug": "premium-upgrade" },
"trace": [ "..." ]
}No match response
{
"matched": false,
"reason": "No matching campaign/audience for placement 'feature_gate'",
"trace": [
{
"campaign": "Onboarding Flow",
"campaign_id": "camp_abc",
"placement_matched": false,
"skipped_reason": "Placement 'feature_gate' not found in campaign placements ['onboarding_complete']"
}
]
}Step 2: Read the Trace
The trace array shows every campaign evaluated, whether the placement matched, and for each audience, whether filters passed. Look for:
placement_matched: false-- the placement name in yourregister(placement:)call does not match any placement in the campaign. Check for typos.filters_matched: falsewithfilter_details-- shows exactly which filter failed, what value was expected, and what the user's actual value was.skipped_reason: "User has entitlement 'pro'"-- the user already has the required entitlement, so the paywall is intentionally skipped.skipped_reason: "Frequency cap exceeded"-- the user has already seen this paywall the maximum number of times for the session/day/total.
Filter detail example
{
"filter_details": [
{
"field": "user.plan",
"operator": "is",
"value": "free",
"actual": "pro",
"result": false
}
]
}This tells you the filter expected user.plan to be "free" but the user's actual value is "pro".
Step 3: Enable SDK Debug Logging
Set the SDK log level to .debug to see the full evaluation in the Xcode console:
var options = AgentWallieOptions()
options.logLevel = .debug
AgentWallie.configure(apiKey: "pk_...", options: options)Look for log lines containing:
Config fetched-- confirms the SDK has a configEvaluating placement-- shows which placement is being evaluatedAudience matched/Audience skipped-- shows filter evaluation resultsPresenting paywall-- confirms a paywall was selected
Step 4: Check Paywall Status
Only published paywalls appear in the compiled config. If the paywall exists but is still in draft status, the SDK will never see it.
curl https://agentwallie.com/api/v1/projects/PROJECT_ID/paywalls \
-H "Authorization: Bearer sk_your_private_key"Check the status field in the response. If it is "draft", publish it:
curl -X POST https://agentwallie.com/api/v1/projects/PROJECT_ID/paywalls/PAYWALL_ID/publish \
-H "Authorization: Bearer sk_your_private_key"Step 5: Verify the Config Endpoint
Fetch the compiled config directly to see what the SDK is receiving:
curl https://agentwallie.com/api/v1/config/pk_your_public_keyCheck that:
- The
campaignsarray is not empty - Your campaign has a placement matching the one you registered
- The
paywallsmap contains the paywall referenced by the variant - The
productsarray contains products needed by the paywall
Buttons Not Responding
Paywall shows but tapping a CTA button does nothing.
Check the action Value
Every cta_button component needs a valid action prop. The valid values are:
| Action | Behavior |
|---|---|
purchase | Initiates StoreKit purchase for the product slot |
select_product | Selects a product in the product picker |
restore | Restores previous purchases |
close | Dismisses the paywall |
open_url | Opens a URL (requires url prop) |
custom_action | Fires delegate callback (requires custom_action_name prop) |
custom_placement | Registers a nested placement |
navigate_page | Navigates to a page in a multi-page paywall |
request_review | Requests an App Store review |
none | No action |
A common mistake is using an invalid action like "subscribe" or "buy" instead of "purchase". The API validates actions on create/update, but if you're seeing issues, verify with:
curl https://agentwallie.com/api/v1/projects/PROJECT_ID/paywalls/PAYWALL_ID \
-H "Authorization: Bearer sk_your_private_key" | jq '.schema.components[] | select(.type == "cta_button") | .props.action'Check SDK Version
Older SDK versions may not support newer action types like request_review or navigate_page. Update AgentWallieKit to the latest version:
https://github.com/cynisca/AgentWallieKit.gitCheck the product Prop
For action: "purchase", the button needs a product prop that references a valid product slot ("primary", "secondary", or "selected"). If product is missing or references a slot that does not exist in the paywall's products array, the purchase will silently fail.
Config Decode Errors
The SDK fails to parse the config, or the config endpoint returns unexpected data.
Validate the Project Config
Use the validate endpoint to check your entire project configuration for structural problems:
curl -X POST https://agentwallie.com/api/v1/projects/PROJECT_ID/validate \
-H "Authorization: Bearer sk_your_private_key"Healthy response
{
"valid": true,
"errors": [],
"warnings": []
}Response with issues
{
"valid": false,
"errors": [
{
"type": "variant_invalid_paywall",
"message": "Variant in audience \"Free Users\" references paywall \"pw_deleted\" which doesn't exist",
"entityType": "experiment_variant",
"entityId": "var_001"
},
{
"type": "invalid_action",
"message": "Paywall \"Premium Upgrade\" component \"cta\": invalid action \"subscribe\". Valid: none, purchase, select_product, restore, close, open_url, custom_action, custom_placement, navigate_page, request_review",
"entityType": "paywall",
"entityId": "pw_abc123"
}
],
"warnings": [
{
"type": "orphan_paywall",
"message": "Paywall \"Old Promo\" is not used in any campaign experiment",
"entityType": "paywall",
"entityId": "pw_old"
}
]
}Common Validation Errors
| Error Type | Meaning | Fix |
|---|---|---|
campaign_no_placements | Campaign has no placements attached | Add at least one placement to the campaign |
audience_no_experiment | Audience exists but has no experiment | Create an experiment with at least one variant for the audience |
variant_invalid_paywall | Variant references a deleted/missing paywall | Update the variant to point to an existing paywall |
invalid_action | Component uses an unrecognized tap action | Use one of the valid action values listed above |
invalid_schema_json | Paywall schema is not valid JSON | Re-upload the paywall schema |
Verify the Config Directly
If you suspect the compiled config itself is malformed, fetch it and validate the JSON:
curl -s https://agentwallie.com/api/v1/config/pk_your_public_key | python3 -m json.toolIf this fails with a JSON parse error, the config endpoint has a server-side issue. Check that all paywall schemas stored in the database are valid JSON.
Product Resolution Failures
The paywall renders but prices show as empty strings, or purchases fail because the product cannot be found.
How Product Resolution Works
- Paywall schema defines abstract product slots (e.g.,
"primary","secondary") - The backend config maps slots to
Productrecords with astoreProductId(e.g.,com.myapp.pro_monthly) - The SDK calls
Product.products(for:)with the store product IDs to fetch live pricing from StoreKit - At render time, expressions resolve to localized prices
If any step fails, prices will be empty or the purchase will not initiate.
Step 1: Check Products Exist
curl https://agentwallie.com/api/v1/projects/PROJECT_ID/products \
-H "Authorization: Bearer sk_your_private_key"Verify that:
- Products exist for the project
storeProductIdmatches exactly what is configured in App Store Connect / Google Play Consolestoreis set to the correct value (apple,google, orstripe)
Step 2: Check Paywall Product Slots
curl https://agentwallie.com/api/v1/projects/PROJECT_ID/paywalls/PAYWALL_ID \
-H "Authorization: Bearer sk_your_private_key" | jq '.schema.products'The paywall schema should have a products array with slot definitions:
[
{ "slot": "primary", "label": "Annual" },
{ "slot": "secondary", "label": "Monthly" }
]If this array is empty or missing, the SDK has no slots to resolve.
Step 3: Verify StoreKit Product IDs
The storeProductId must exactly match the product ID in App Store Connect. Common mistakes:
- Extra whitespace in the store product ID
- Using the wrong bundle identifier prefix
- Product not yet approved / in review in App Store Connect
- Product ID is for a different app
Expression resolution for product prices is currently limited in the iOS SDK. Some expressions may resolve to empty strings. Workaround: Use the product picker component which fetches live StoreKit prices, or hardcode display prices in text content.
Step 4: Enable Debug Logging
With .debug log level enabled, look for SDK logs containing:
StoreKit products fetched-- confirms products were loaded from StoreKitProduct resolution failed-- shows which store product ID could not be foundNo StoreKit product for-- the store product ID does not match any product in App Store Connect
Common Mistakes
Quick reference for the most frequent issues.
| Symptom | Likely Cause | Fix |
|---|---|---|
| Paywall never shows | Paywall status is draft | Publish the paywall |
| Paywall never shows | Placement name typo | Compare register(placement:) string with campaign placement names |
| Paywall never shows | No matching audience | Run dry-run to see which filters fail, then fix user attributes or filter rules |
| Paywall never shows | User has entitlement | Expected behavior -- user already has access |
| Paywall never shows | Frequency cap hit | Clear frequency state or adjust the cap |
| Paywall never shows | Campaign status is not active | Activate the campaign |
| Buttons do nothing | Invalid action value | Use a valid action: purchase, restore, close, etc. |
| Buttons do nothing | Missing product prop on purchase button | Add "product": "primary" or "product": "selected" |
| Prices are empty | storeProductId mismatch | Verify the ID matches App Store Connect exactly |
| Prices are empty | Expression resolution bug | Use hardcoded prices or a custom_view (see Known Limitations) |
| Prices are empty | Products not in config | Create products via the API and verify they appear in the config |
| Config fetch fails | Wrong API key type | Use the public key (pk_...) for the config endpoint, not the private key |
| Config fetch fails | Rate limited | SDK is fetching too frequently -- ensure caching is enabled (default: 5 min) |
| Validation error on create | Invalid action in schema | Check action values against the valid list |
| Validation error on create | Missing required fields | name, slug, and schema.version are required |
PAYWALL_NOT_FOUND | Wrong paywall ID | List paywalls with GET /v1/projects/:id/paywalls to find the correct ID |
FOREIGN_KEY_CONSTRAINT | Referenced record missing | Create the parent entity (project, campaign, etc.) first |
UNIQUE_CONSTRAINT | Duplicate slug or name | Choose a different slug |
Useful Diagnostic Commands
Fetch compiled config
curl -s https://agentwallie.com/api/v1/config/pk_your_public_key | python3 -m json.toolList all campaigns
curl https://agentwallie.com/api/v1/projects/PROJECT_ID/campaigns \
-H "Authorization: Bearer sk_your_private_key"Validate project
curl -X POST https://agentwallie.com/api/v1/projects/PROJECT_ID/validate \
-H "Authorization: Bearer sk_your_private_key"Dry-run a placement
curl -X POST https://agentwallie.com/api/v1/projects/PROJECT_ID/dry-run \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk_your_private_key" \
-d '{
"placement_name": "YOUR_PLACEMENT",
"user_attributes": { "plan": "free" }
}'View changelog
curl "https://agentwallie.com/api/v1/projects/PROJECT_ID/changelog?limit=10" \
-H "Authorization: Bearer sk_your_private_key"