webinarninja — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited webinarninja (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Beheer WebinarNinja webinars voor Dubai Property via Playwright automation.
Account: [email protected] Subdomain: dubai-property.webinarninja.com Session opslag: ~/.auth/webinarninja.json
Seyed's instructie (16 april 2026): Maak NOOIT een nieuw webinar aan via "Create New Webinar". Dupliceer altijd de meest recente webinar via More → Clone. Hiermee worden alle settings correct overgenomen:
Daarna aanpassen:
EMAIL = "[email protected]"
PASSWORD = # zie ~/.openclaw/secrets/webinarninja of WEBINARNINJA_PASSWORD
AUTH_FILE = Path.home() / ".auth" / "webinarninja.json"
# Login script
await page.goto("https://my.webinarninja.com/login")
email_input = page.locator("input[name='email']:visible")
pwd_input = page.locator("input[name='password']:visible")
await email_input.fill(EMAIL)
await pwd_input.fill(PASSWORD)
await page.keyboard.press("Enter")
await page.wait_for_load_state("networkidle")
# Save session
storage = await page.context.storage_state()
AUTH_FILE.write_text(json.dumps(storage))De clone API (/api/v1/cloned-webinars) werkt niet headless — geeft altijd "invalid date range". Gebruik de UI:
# Click More → Clone → OK
await page.goto("https://dubai-property.webinarninja.com/app/webinars/dashboard")
await page.locator("text=More").first.click()
await page.wait_for_timeout(800)
# Clone via menu
await page.evaluate("""
() => {
const btns = document.querySelectorAll('button.btn.-menu');
for (const btn of btns) {
if (btn.innerText.trim() === 'Clone') { btn.click(); return; }
}
}
""")
await page.wait_for_timeout(1500)
# Click OK in confirmation dialog (gebruik force=True)
ok_btn = page.locator("button.btn.-primary.-fixed:has-text('OK')")
await ok_btn.click(force=True)
await page.wait_for_timeout(10000) # Clone duurt lang!Na clone → reload → check ?filter=past of check de upcoming list via API:
webinars = await page.evaluate("""
async () => {
const r = await fetch('/api/v1/users/4632613/webinars?perpage=500&filter[archived]=0&type=all', {
credentials: 'include', headers: {Accept: 'application/json'}
});
const d = await r.json();
return d.data.map(w => ({id: w.id, title: w.title}));
}
""")Navigeer naar edit URL:
https://dubai-property.webinarninja.com/account/creator/webinars/{NEW_ID}/live/update/detailsawait page.evaluate(f"""
() => {{
const el = document.querySelector('#live-webinar-0-title');
el.focus();
el.value = '{new_title}';
el.dispatchEvent(new Event('input', {{bubbles: true}}));
el.dispatchEvent(new Event('change', {{bubbles: true}}));
}}
""")date_vis = page.locator("input[name='webinar[0][date]']:visible")
await date_vis.click(click_count=3)
await date_vis.fill("May 13, 2026")
await page.keyboard.press("Tab")for step in range(5):
btn = page.locator("button:has-text('Save & Next'), button:has-text('Skip & Finish')")
if await btn.count() > 0:
await btn.first.click()
await page.wait_for_load_state("networkidle", timeout=15000)
await page.wait_for_timeout(2000)
else:
breakembed_url = f"https://dubai-property.webinarninja.com/account/creator/integrations/registration-forms/create/step-1?webinar_id={new_id}&webinar_type=live"
await page.goto(embed_url)
await page.wait_for_timeout(2000)
save_btn = page.locator("button:has-text('Save')")
await save_btn.first.click()
await page.wait_for_timeout(2000)
# Get embed ID
source = await page.content()
embed_ids = re.findall(r'embedding-form[/-](\d+)', source)
# embed_ids[0] is het embed ID (bijv. 10431, 10432)Embed code template:
<div data-rt-embed-type="true">
<!-- WebinarNinja embedding registration form -->
<iframe style="min-width: 320px; width: 100%; height: 420px; border: 0; overflow: hidden;"
class="webinarninja-embedding-registration-form-{EMBED_ID}"
src="https://my.webinarninja.com/embedding-form/{EMBED_ID}"></iframe>
<script>
function updateIframe(e){switch(e.type){case"resizeIframe":this.style.height=e.height+"px";break;case"iframeDownloadFailed":this.hidden=!0;break;default:return}}
window.addEventListener("message",function(e){var a=document.querySelectorAll(e.data.id);[].forEach.call(a,function(a){updateIframe.call(a,e.data)})});
</script>
</div>Webinars collectie ID: 680a6e5211acc5d3f7e89a0b
curl -X PATCH "https://api.webflow.com/v2/collections/680a6e5211acc5d3f7e89a0b/items/{ITEM_ID}" \
-H "Authorization: Bearer $WEBFLOW_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fieldData": {
"name": "...",
"slug": "...",
"heading": "...",
"datum": "...",
"tijd": "...",
"webinar-code-rich": "...",
"cover": {"fileId": "...", "url": "..."}
}
}'# Create
curl -X POST ".../items" -d '{...}'
# Publish
curl -X POST ".../items/publish" -d '{"itemIds": [...]}'
# Site publish (wacht 35s na rate limit)
sleep 35 && curl -X POST ".../sites/{site_id}/publish" \
-d '{"publishToWebflowSubdomain": true, "customDomains": ["690ce65abb41e489c9bc35e1", "690ce659bb41e489c9bc35b7"]}'MINIMAX_KEY="sk-cp-..." # zie ~/.openclaw/secrets/.env
curl -X POST "https://api.minimax.io/v1/image_generation" \
-H "Authorization: Bearer $MINIMAX_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "image-01",
"prompt": "Professional webinar cover...",
"aspect_ratio": "16:9",
"response_format": "url",
"n": 1,
"prompt_optimizer": true
}'https://dubai-property.webinarninja.com/live-webinars/{WEBINAR_ID}/register
force=True| Field | Type | Beschrijving |
|---|---|---|
| name | PlainText | Interne naam |
| slug | PlainText | URL slug |
| heading | PlainText | Hoofdtitel op pagina |
| subheading | PlainText | Ondertitel |
| paragraph-1 | RichText | Intro tekst |
| date-time | PlainText | Datum + tijd (leesbaar) |
| datum | PlainText | Alleen datum |
| tijd | PlainText | Alleen tijd |
| cover | Image | Cover afbeelding |
| heading-2 | PlainText | "Wat bespreken we?" |
| paragraph-2 | PlainText | Spreker bio |
| paragraph-2-picture | Image | Spreker foto |
| bullet-1 t/m bullet-5, bulet-6 | PlainText | Onderwerpen |
| webinar-code-rich | RichText | Embed iframe code |
| seo-meta | PlainText | SEO beschrijving |
| webinar-passed | Switch | true = verlopen |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.