Skip to content

Debugging Marketplace

Use this when Marketplace account linking, public domain verification, catalogue browsing, install authorization, heartbeat, diagnostics, or update notices fail.

sequenceDiagram
participant Admin
participant CMS as Capell CMS
participant App as Capell App
participant Public as Public Host
Admin->>CMS: Connect account
CMS->>App: Create account connection session
App-->>Admin: Approval URL
Admin->>App: Approve site
App->>CMS: Authenticated callback with code/state
CMS->>App: Exchange code
App-->>CMS: Instance ID and signing secret
Admin->>CMS: Verify public domain
CMS->>Public: Write challenge file
CMS->>App: Request verification
App->>Public: Fetch .well-known challenge
App-->>CMS: Verified instance/domain
CMS->>App: Heartbeat and install authorization requests

Account linking is the normal setup path. Public domain verification is a stronger production signal and requires a real public hostname.

Terminal window
php artisan config:show app.url
php artisan config:show capell-marketplace.marketplace.base_url
php artisan config:show capell-marketplace.marketplace.webhook_url
php artisan route:list --name=capell-marketplace

The default API URL is:

CAPELL_MARKETPLACE_URL=https://capell.app/api/v1
select connection_session_id, claimed_domain, app_url, callback_url, status, expires_at, completed_at, last_error
from marketplace_account_connection_sessions
order by id desc
limit 5;
StatusMeaningFix
pendingAdmin has not returned from Capell App yetComplete the current approval URL before it expires.
completingCallback reserved the session while exchanging codeWait briefly; if stuck, start a new connection.
failedRemote request or validation failedRead last_error, fix config/session, retry.
expired10-minute window passedStart a fresh connection.
completedInstance should existCheck marketplace_instances.

Do not reuse old approval URLs after starting a newer account connection.

select domain, challenge_id, challenge_path, status, expires_at, last_error
from marketplace_registration_sessions
order by id desc
limit 5;

Fetch the exact public challenge URL:

Terminal window
curl -i https://your-domain/.well-known/capell/marketplace/chal_EXAMPLE

Expected result: 200 OK, Content-Type: text/plain, and the stored challenge token body.

Common blockers:

  • www.example.com was entered but example.com is being fetched, or the reverse.
  • The challenge route is behind auth, maintenance mode, or a CDN rule.
  • Static file handling bypasses Laravel/public files incorrectly.
  • The session expired.
  • The domain is local-only, such as .test, .localhost, localhost, 127.*, or an IP address.
select instance_id, connection_mode, account_email, verified_domains, last_heartbeat_at
from marketplace_instances
order by last_heartbeat_at desc
limit 5;

Browsing the catalogue only proves the catalogue endpoint works. Installing also needs a connected instance, entitlement/licence state, domain policy, and local platform compatibility.

Check local package versions:

Terminal window
composer show capell-app/core filament/filament livewire/livewire laravel/framework

If the catalogue appears stale in local debugging:

Terminal window
php artisan cache:clear

Use targeted browser refresh controls in production rather than broad cache clears.

select instance_id, last_heartbeat_at
from marketplace_instances
order by last_heartbeat_at desc
limit 5;
select source, checked_at, capell_version, metadata
from marketplace_update_advisory_snapshots
order by checked_at desc
limit 5;

Heartbeat needs:

  • Marketplace API base URL;
  • public webhook/callback URL from CAPELL_MARKETPLACE_WEBHOOK_URL or APP_URL;
  • known instance ID;
  • outbound network access to the Marketplace API.
it('fails account connection when app url has no host', function (): void {
config(['app.url' => '']);
StartMarketplaceAccountConnectionAction::run();
})->throws(RuntimeException::class, 'APP_URL must include a valid host');
it('serves only matching pending challenge domains', function (): void {
MarketplaceRegistrationSession::factory()->create([
'domain' => 'example.com',
'challenge_id' => 'chal_TEST',
'challenge_token' => 'secret-token',
]);
$this->get('https://example.com/.well-known/capell/marketplace/chal_TEST')
->assertOk()
->assertSee('secret-token');
});
it('does not phone home without a connected instance', function (): void {
expect(PhoneHomeAction::run())->toBeFalse()
->and(PhoneHomeAction::lastFailureMessage())->toContain('not connected');
});