Set up the widget
Create a widget in the dashboard and set its branding, callbacks, captcha, rate limits and circuit breaker.
Step 1: Create Widget in Dashboard
Before integrating, create a widget in your Akedly dashboard.
Dashboard-Only Configuration
Widget creation is done entirely through the dashboard UI. There is no API for creating widgets. This ensures proper security configuration and prevents unauthorized widget creation.
Dashboard Navigation
- Log in to your Akedly dashboard at https://app.akedly.io
- In the left sidebar, open Authentication → Setup → Widgets
- Click Create Widget (the button reads Create Your First Widget on the empty state)
The sidebar is product-scoped — two items are named Pipelines
Pipelines appears twice: once under Authentication → Setup (OTP verification pipelines — the ones a widget binds to) and once under Utilities → Setup (a separate, unrelated product surface). Every reference to "Pipelines" on this page means the one under Authentication.
Basic Info
The widget form is split into collapsible sections, navigated from the Sections rail: Basic Info, Pipeline, Passkeys, Captcha, Rate Limiting, Circuit Breaker, and Developer.
- Name
- Widget Name
- Type
- string
- Description
Internal name for your reference (e.g., "Production Login Widget")
- Name
- Description
- Type
- string
- Description
Optional description of where this widget is used
Branding
Branding is not configured in the widget form. The form only carries a Branding & theme link card; the appearance of the hosted widget is edited in the dedicated Branding Studio at /admin/widgets/:id/branding, which has a live preview alongside its tabs.
- Name
- Brand
- Type
- tab
- Description
Logo, brand name, and core brand colors.
- Name
- Theme
- Type
- tab
- Description
Light/dark theme, surfaces, borders, and radii.
- Name
- Typography
- Type
- tab
- Description
Font family and type scale.
- Name
- Layout
- Type
- tab
- Description
Spacing, alignment, and overall widget composition.
- Name
- Components
- Type
- tab
- Description
Per-component styling (buttons, inputs, and other controls).
- Name
- Content
- Type
- tab
- Description
Widget copy — headings, labels, and helper text.
Rather than starting from a blank theme, the Brand tab offers presets — Akedly, Midnight, Minimal, Corporate and Playful. Pick one as a starting point and fine-tune from there; your custom copy is kept when you switch preset.
The preview beside the tabs is live and renders the real widget: loading, OTP entry, success and error always, plus the captcha and passkey screens when those are enabled on the widget. Toggle Show all screens to see them side by side, and switch the preview between desktop, tablet and mobile — each renders at that device's real width, so the hosted widget's own responsive behaviour is what you see. Edits are not applied until you press Save; Reset discards them and returns to the last saved branding.
Branding is a per-widget setting, so a widget must exist before you can open its Branding Studio. Create the widget first, then open it and follow the Branding & theme card.
Webhooks
V2.0 Widgets support two types of callbacks. See Pipeline Setup for detailed configuration, or navigate to Authentication → Setup → Pipelines → select your pipeline → Callback URLs.
Front-end Redirect URL (required)
Where to redirect the user after authentication completes.
Example:
https://yourapp.com/auth/callback
Success Redirect:
https://yourapp.com/auth/callback?status=success&transactionId=mtx_abc123&attemptId=attempt_xyz789×tamp=2025-01-16T12:00:00Z&meta_userId=user_12345&meta_orderId=order_abc789
Failure Redirect:
https://yourapp.com/auth/callback?status=failed&error=INVALID_OTP&transactionId=mtx_abc123&attemptId=attempt_xyz789×tamp=2025-01-16T12:00:00.000Z&meta_userId=user_12345&meta_orderId=order_abc789
A request-otp call whose first send could not be confirmed (OTP_DISPATCH_OUTCOME_UNKNOWN) can also return this failure redirect in its error body. That path sends no backend webhook. See the Error Reference (OTP dispatch responses).
Back-end Webhook URL (required)
Webhook endpoint to receive verification events. Akedly sends a POST request with complete verification details immediately after successful or failed verification.
Example:
https://yourapp.com/api/webhooks/akedly
Frontend Redirect Query Parameters:
- Name
- status
- Type
- string
- Description
"success" or "failed"
- Name
- isTest
- Type
- string
- Description
"true"on a successful test-pair redirect; omitted for production verifications.
- Name
- transactionId
- Type
- string
- Description
The transaction ID from the verification flow
- Name
- attemptId
- Type
- string
- Description
The original attemptId you created
- Name
- timestamp
- Type
- string
- Description
ISO 8601 timestamp of when authentication completed
- Name
- error
- Type
- string
- Description
Error code (only present if status=failed)
- Name
- meta_*
- Type
- string
- Description
Custom metadata fields from
publicMetadata. Each key is prefixed withmeta_. Object values are JSON stringified.
Captcha Settings
Protect your widget from automated attacks with built-in captcha verification.
- Name
- Enable Captcha
- Type
- boolean
- Description
Always enabled by default. Captcha verification is mandatory for all widget interactions and cannot be disabled.
- Name
- Require Cloudflare Turnstile
- Type
- boolean
- Description
Always enabled. Cloudflare Turnstile is required to protect your quotas and widgets from bot spam. This setting cannot be turned off to ensure maximum security against automated abuse.
Rate Limiting
Control authentication and OTP request limits to protect your widget from abuse. Rate limits are configured across three dimensions: per phone number, per device ID (fingerprinting), and per widget.
AI-Powered Rate Limit Analysis: If you have sufficient historical data, use the "Get Recommendations" feature to analyze your usage patterns and suggest optimal rate limits that balance security with user experience.
Default values are suitable for 95% of use cases. Only adjust if you have specific requirements. If you expect bursty traffic, focus on adjusting the per-minute value—the per-hour and per-day values will auto-calculate based on safe ratios.
Widget Attempts
Widget attempts track iframe loads and authentication attempts, regardless of whether they pass captcha or fingerprinting. A failed captcha still counts as an attempt, even if no OTP is sent.
OTP Requests
OTP requests track actual One-Time Password deliveries. These limits apply only when an OTP is successfully sent to the user.
Cooldown Duration
- Name
- Cooldown duration
- Type
- number
- Description
Default: 300000 (5 minutes). Stored on the widget, but the V2 rate limiter does not read it. When a V2 request is rate limited, the wait comes from the
retryAfterandcooldownSecondsfields in the 429 response, which are measured from the oldest counted attempt in the window that was breached.
No implicit per-request cooldown on V2
The platform-wide 60-second cooldown between OTP requests does not apply to V2 widget requests — this endpoint skips it deliberately, because the widget's own per-phone and per-device limits above already govern the pace. These limits REPLACE that cooldown; they are not the only guard a V2 integration meets. The resend limit always applies. Zero-verify bans apply on top only when BOTH are true: Akedly has enforcement switched on platform-wide (it is off by default, and its shadow setting records what it would have blocked without returning a refusal), AND the pipeline has not disabled them. Live test-pair attempts are exempt, so a request using the test pair's number will never show you a ban — check ban handling with any other number, which is treated normally even while dev mode is on. Treat a ban response as something to handle if it arrives, never as protection you can assume is active.
Circuit Breaker
The circuit breaker is your final layer of defense that automatically suspends your widget when abnormal traffic patterns are detected. It works alongside captcha and rate limiting to provide comprehensive protection.
Keep Circuit Breaker Enabled. Disabling the circuit breaker removes your last line of defense against sophisticated attacks. The circuit breaker is calibrated to only trigger during genuine flood attacks—if it activates, it means it protected you from a real threat.
Why Circuit Breaker Matters:
- Blocks Coordinated DDoS Attacks: Detects and stops distributed attacks from multiple sources attempting to overwhelm your widget
- Protects Your Quota: Prevents sophisticated attacks from draining your API quota and incurring unexpected costs
- Last Line of Defense: Catches threats that bypass captcha and rate limiting (while rare, it's possible with advanced attack techniques)
- Automatic Recovery: Temporarily suspends the widget during attacks and automatically resumes normal operation when the threat subsides
After Widget Creation
Once you create the widget, you'll receive credentials needed for API integration:
- Name
- Widget ID
- Type
- string
- Description
Internal identifier (e.g.,
widget_a1b2c3d4...)
- Name
- Public Key
- Type
- string
- Description
Used in API requests (e.g.,
pk_x1y2z3...)
- Name
- Widget Secret (SAVE THIS NOW)
- Type
- string
- Description
Used to sign API requests with HMAC-SHA256
The secret is shown once and cannot be rotated
The widget secret is displayed only at creation — copy it then, as shown in the screenshot. There is no regenerate or rotate action for a widget secret anywhere in the product, so losing it is not self-service recoverable: you would have to create a new widget (and repoint your integration to its new public key) or contact support@akedly.io.
Store your secret key in environment variables. Never commit it to version control or expose it to frontend code.
