Technical Documentation

4 articles available in this topic

Buttons guide users through your product, help them make decisions, and move them toward their goals.

And when you’re designing for action, buttons should be your first choice. That’s what they’re built for—and it’s what users expect. Following user expectations and established UI patterns is the most reliable way to create effective, user-centered products.

Great button design goes beyond color and shape. It’s about clarity, consistency, hierarchy, placement, and even the words you choose. When you get those right, users move through your product with confidence.

Here, we’ll walk through the key principles of effective button design—from making buttons look clickable to writing button copy that builds trust. You’ll also learn how to wireframe buttons early in your process, so you can map out smart, intuitive flows before you ever touch code.

Let’s get started!

Button structure & hierarchy is everything
At a glance, users need to know what’s clickable and what’s not. Most people scan a screen quickly and subconsciously, looking for visual cues to help them take action.

Your button design should clearly communicate, “Hey, you can click me!” And thankfully, you don’t need anything fancy to get there. A few foundational design principles—like hierarchy, color, shape, and contrast—can do most of the work.

Good news: users already have mental models from using other websites and apps. Lean into that familiarity. When your buttons follow common patterns, users don’t have to think twice about what to click.

1. Make buttons look like buttons
Users already know what a button looks like. You don’t need to reinvent it. In fact, you shouldn’t.

Over-styled or ambiguous designs might look pretty in a mockup, but if a user can’t immediately tell something is clickable, you’re adding friction. Make sure buttons have familiar visual cues: dimensionality, contrast, padding, and hover or active states.

❌ Don’t: Style your CTA like body text.

✅ Do: Make it visually distinct and obviously clickable.

Oh, and quick reminder: buttons ≠ links. (We’ll get to that in a minute.)

Button design best practices
Buttons that look like buttons (left) vs. buttons that don't (right)
2. Primary and secondary buttons should look different
In any given interface, one action should stand out as the next best step. That’s your primary button. It could be “Sign Up,” “Save Changes,” or “Get Started.” Whatever it is, it deserves visual priority.

Secondary actions—like “Cancel,” “Skip for now,” or “Back”—should be styled to reflect their lower priority. You’re not hiding them, just making sure they don’t pull attention from your main CTA.

Ways to create visual distinction:

Use a bold or branded color for primary buttons
Use outlines, lighter fills, or grayscale for secondary buttons
Maintain consistent sizing, but vary the weight or prominence
Primary and secondary buttons example
Source
On the Gusto homepage, the primary and secondary calls to action are clearly differentiated through button styling. The “Create free account” button uses a bold, high-contrast color to stand out as the primary action, while the “How Gusto works” button is outlined, signaling a secondary option. This hierarchy guides new users toward the preferred next step—signing up—while still offering an easy path to self-education.

The same structure appears in the navigation bar: “Create free account” remains the bold, primary action, while “How it works” is styled with the outline again and “See demo” is also linked as a tertiary option.

Visual hierarchy isn’t just about aesthetics—it’s about nudging behavior.

3. Buttons are not links
Buttons and links are not interchangeable—and treating them like they are is a fast way to confuse your users.

Buttons initiate actions: submitting a form, starting a process, opening a modal.

Links are for navigation: jumping to another page or section.

Styling them the same way leads to hesitation or misclicks. Users shouldn’t have to pause and wonder: “Is this going to take me somewhere or do something?”

Clear distinctions help:

Buttons = solid fills, rounded corners, hover animations
Links = underlined text or basic text styles, no container
How to use button and links together
Links go somewhere, buttons do something
If your UI uses one where the other makes more sense, you’re adding invisible friction. Over time, these tiny moments compound and frustrate users—especially on mobile.

4. Don’t have more than one primary action button on the screen at a time
Let’s be clear: your UI can have more than one button. Just not more than one primary button.

The whole idea of a primary action is to focus user attention. If every button screams for attention, none of them get it. Instead of guiding the user, you overwhelm them.

This is especially important on small screens. On mobile, you often have just one or two seconds to direct someone’s attention. If they see three competing buttons, they’ll either get stuck or tap the wrong one.

Cancellation page button design example
Cancel account page of audible.com
Audible is an infamously poor example of this. On their cancel account page, every button is styled as primary. It’s intentional: they want you to second-guess the cancellation process. As a user, it forces you to slow down, read closely, and hope you don’t click the wrong one.

Good UX builds trust. Confusing button hierarchy breaks it.

Button placement should feel natural
You’ve got a beautifully designed button—now where does it go?

Button placement isn’t just about layout preferences or aesthetics. It’s about flow. A well-placed button supports the natural progression of a task and keeps users moving forward. A misplaced one creates friction, confusion, or even abandonment.

Let’s talk about where buttons should live and why.

5. Place primary actions where users finish their task
Users expect the most important button—your primary action—to appear at the end of the process or form. That might be at the bottom of a modal, the right side of a two-button pair, or the last step of a multi-page flow.

It’s a small detail with a big impact: putting the button where users already are when they’re done thinking makes it easy to act.

Button design placement example
Button design placement left vs right example
Look at this scanning pattern, which do you think is easiest?

User scanning patterns
How your users will scan information
6. Avoid floating or disconnected buttons
Buttons that float in strange corners or appear far away from the task they relate to can confuse or frustrate users. Keep your calls-to-action (CTAs) close to the action. Simple enough.

Floating button design
Floating buttons feel disjointed
In long forms or mobile views, fixed buttons (that stay anchored to the bottom of the screen) can reduce friction and improve task completion rates. But they should never block content or be too aggressive.

When used right, sticky CTAs make interfaces feel faster and more fluid.

Related Article: SaaS website design: Lessons from real users

Button wording needs to build confidence
Design gets attention. Wording earns the click.

No matter how visually perfect your button is, it’s the words that ultimately seal the deal. The right label gives users the clarity (and confidence) to move forward. The wrong label? It can cause hesitation, drop-off, or worse… accidental actions.

While you don’t need final content, thinking through wireframe website copy—especially button text—early on helps map out a stronger user experience.

Turn “I have an idea…” into “Here’s how it works.”
Sketch your next product concept in minutes with Balsamiq.

Try it free
Try Balsamiq Cloud for free
Let’s walk through how to write button copy that’s clear, helpful, and user-friendly.

7. Keep it short (and specific)
Button copy shouldn’t be a sentence—it should be a signal.

The most effective buttons are 1 to 3 words max, using direct, specific verbs. The tighter the copy, the easier it is to scan, process, and click with confidence.

Concise button copy
Source
This isn’t about being robotic—it’s about reducing friction. Say just enough to guide the action, and let the surrounding context do the rest.

If your button needs a paragraph of explanation, your UI flow might be doing too much.

8. Say exactly what happens next
Your button text should describe the result of clicking. Vague labels like “Submit,” “OK,” or “Click Here” don’t tell the user anything about what’s coming.

Instead, use action-oriented language that sets the right expectation. If your button label could work on any page of your site, it’s probably too generic.

Take Dropbox for example. To create a new file, you click on the Create New File button. The next step is to choose where the file will be saved. Once you select a folder, you click on the “Create” button. It’s very clear what actions you are taking based on the text used.

Button flow design
Create New File UX from dropbox.com
9. Match the button to the moment
Your primary button should align with what the user is doing right now—not what you think comes next in your flow.

If a user is filling out a download form, don’t label your button “Next.” Label it “Download Report.” It gives the user confidence that they’re completing the task at hand—not getting pulled into something else.

This is especially critical in multi-step forms, confirmation screens, or modals. The more aligned your button copy is with the action, the smoother the experience.

10. Avoid words that can potentially cause confusion
If your button performs a destructive or irreversible action, it needs to say so, clearly.

❌ Bad:

“OK”
“Yes”
“Confirm”
✅ Better:

“Delete Account”
“Remove File Permanently”
“Cancel My Subscription”
When a button could have big consequences, your wording should slow users down just enough to be sure. Bonus points if you include an undo option or confirmation step.

At Balsamiq, deleting one of your projects is a huge deal. We make users take the extra step of writing the word DELETE to ensure they really want to delete a project. This allows users extra time to think through their actions and avoid mistakes.

Destructive button design
Destructive CTAs require extra steps
If you can’t go the extra step of having a user write the word DELETE, adding a simple Undo command after the deletion will go a long way in helping avoid paying for a hasty mistake.

Button design for undo action
Users still have a way to undo their action
11. Speak the user’s language, not yours
Avoid product jargon or internal terminology. Your team may know what “Enable Smartflow” means—but your users probably don’t.

Instead, write button text in plain language your audience already understands. Use verbs they’d naturally say out loud when describing the action.

TL;DR button wording should feel obvious.

Button size & spacing matters
You’ve nailed the look. The label is clear. But is your button easy to actually click—especially on mobile?

Button size and spacing play a huge role in usability. Too small? Users will miss it or mis-tap. Too close together? They’ll hit the wrong one. Give your buttons the space they need to feel touch-friendly, intentional, and frustration-free.

12. Design for fingers, not cursors
On desktops, users have a precise pointer. On mobile? They’re using their thumbs and that’s not always easy.

A good rule of thumb 😉:

Touch targets should ideally be 44x44 pixels.

Ideal button sizes and tap areas
Various button sizes can indicate hierarchy
This size gives enough surface area to press comfortably without precision. A button that’s too small to tap on the first try is too small, period.

And yes—this goes for icons too. If an icon is acting as a button, it needs to be finger-friendly.

13. Use padding to create comfortable tap zones
It’s not just the button size—it’s the space around it that matters, too.

Even if your button shape is relatively small, generous padding inside the element (and margin between buttons) can create a forgiving touch zone. Think of padding as a buffer that helps the button feel solid and pressable.

❌ Bad:

Tightly packed rows of tiny buttons
Minimal or no spacing between CTAs (especially on mobile)
✅ Good:

A clearly defined button with internal padding
Adequate margin between it and nearby elements
Button color isn’t just about looks
Color does more than make buttons look nice. It tells users what’s important, what’s safe, and what happens when they click.

Whether it’s a bold primary button or a subtle hover state, color and contrast help users scan, understand, and take action faster. But too much or too little styling? That’s where confusion creeps in.

Here’s how to use color and contrast intentionally, without overwhelming the interface.

14. Give color meaning
The color of your button should tell users something. It’s not just for brand alignment—it communicates priority and purpose.

Button color best practices
Button color can do a lot of heavy lifting
Here’s a common button color hierarchy:

Button type Example color use
Primary Brand color
Secondary Neutral or outlined
Destructive Red or high-alert tone
Disabled Muted or ghost button

Be consistent. If blue means “primary action” on one screen, it should mean the same thing everywhere. That way, users learn what to expect and act faster. More on that a little later.
15. Use contrast to make buttons stand out
​​Your button text needs to be readable. And your buttons themselves should stand out just enough to be seen immediately, but not so much that they overshadow everything else.

Also make sure you check all button states—default, hover, focus, disabled—for readability.

Aim for high contrast between:

Button background and its container
Button text and the button background
💡 Pro Tip: Use tools like WebAIM’s contrast checker

Button consistency is key
When buttons look and behave the same across your pages, users don’t have to stop and think—they just know what to do.

Consistency isn’t just a nice-to-have, it’s one of the most important principles of effective wireframes.

In button design, consistency helps build user trust, improves usability, and reduces friction across different screens and workflows. Whether someone’s creating an account, saving a form, or dismissing a message, your buttons should feel familiar.

16. Keep the same styles across screens and flows
A primary button on your dashboard should look the same as a primary button in a modal. Same goes for hover states, disabled states, and loading states.

Look for consistency in:

Shape (rounded corners? pill-shaped?)
Color
Font size, weight, & capitalization
Padding and alignment
Button design consistency
Different button shapes are just confusing
17. Avoid styling buttons differently “just because”
Not every new feature or page needs its own button style. In fact, the more custom button styles you add, the harder it becomes for users to trust what’s clickable—and for your team to maintain consistency.

When in doubt, reuse what already exists. Your design system (or even a shared Balsamiq wireframe) can help make that decision easier.

Good design systems are made of reusable parts, not one-off choices.

Better buttons, better Experiences
Designing for action is one of the most important parts of any user interface. Your buttons guide people through tasks, help them make decisions, and move them closer to their goals.

When buttons are confusing, inconsistent, or poorly placed, users hesitate—or worse, they bail. When buttons are clear, consistent, and easy to use, everything flows.

Whether you’re designing a sign-up form, an e-commerce checkout, or a simple settings panel, thoughtful button design makes the difference between “I think this is right…” and “Let’s go.”

A good button isn’t just a design element—it’s a moment of clarity.

🎓 Want to dive deeper into how design influences behavior? Check out our course on The Psychology of UI Design.

Designing buttons just got a lot easier
Try Balsamiq Cloud free for 14 days. Map out your screens, test button placement and wording early, and give your team a clear direction—before a single line of code. It’s the fastest way to build interfaces that feel intuitive from the first click.

Didit API Integration Guide

This guide compiles the full details from the Didit API documentation, covering introduction, setup, workflows, integration methods for web and mobile, no-code options, webhooks, verification statuses, rate limiting, and pricing. It is based on the available reference materials to provide a comprehensive, step-by-step resource for integrating Didit’s AI-native identity verification platform.

#

Introduction

Didit is an AI-native identity platform designed to help developers and businesses verify users instantly, combat sophisticated fraud, and reduce costs by up to 70% without compromising user experience (UX), privacy, or control.

##

Problems with Legacy Identity Verification
Traditional systems suffer from:
- **Exorbitant Costs**: $1–$3 per check with minimum commitments.
- **Manual Reviews**: Slow and error-prone decisioning.
- **Opaque Pricing & Sales Loops**: Testing requires sales interactions.
- **Developer-Hostile Integration**: Poor documentation and clunky SDKs leading to weeks-long setups.
- **Bundled, Inflexible Products**: Forced purchases of unnecessary features via contracts.
- **Not Ready for AI Fraud**: Inadequate detection of deepfakes, spoofing, and synthetic identities.
- **Slow & Painful User Flows**: 60–90 second processes causing high drop-off rates.

##

Didit V2 Principles
Didit aims to build an open, modular identity layer for the internet, powered by AI in a developer-first manner.

| Principle | Description |
|-----------------|-------------|
| **AI-Native** | Fully automated verifications detecting spoofs and synthetic fraud in milliseconds. |
| **Developer-First** | Open docs, clear pricing, instant sandbox—integrate in hours. |
| **Modular & Flexible** | Compose workflows with features like IDV, AML, NFC, Liveness, Face Match, Age-Check. |
| **Affordable** | Free Core KYC, prepaid credits, pay only for successful checks—up to 70% cheaper. |

##

Use Cases
- Seamless user onboarding (KYC / KYB).
- Age & geolocation compliant access.
- Fraud-proof face authentication.
- AML / politically exposed person screening.
- Self-sovereign identity wallets.
- Global ID document verification.

##

Getting Started
1. Create an account for instant sandbox access.
2. Choose integration: No-Code Workflows via Console or APIs & SDKs for custom builds.
3. Launch your first verification in minutes. Start with the Quick Start Guide.

#

Quick Start

Follow these steps to create a Didit account, configure a workflow, secure an API key, and launch your first verification session.

##

Step 1: Create Your Free Didit Account
1. Visit [business.didit.me](https://business.didit.me).
2. Sign up with a business email and authenticate via magic login link.
3. Set up an Organization workspace for managing workflows, API keys, and verifications.

##

Step 2: Build Your First Verification Workflow
1. In the Console, go to **Verifications → Workflows → Create New**.
2. Select a template:
- **KYC**: Standard onboarding.
- **Adaptive Age Verification**: Selfie check with document fallback.
- **Biometric Authentication**: Password-less selfie login.
- **Address Verification**.
3. Customize by adding blocks (e.g., Liveness Detection, Face Match, AML, NFC) and fallback logic.

##

Step 3: Configure Webhook & Copy API Key
1. Navigate to **Verifications → Settings → API & Webhooks**.
2. Add your Webhook URL for status updates.
3. Copy and securely store your API Key (avoid frontend exposure).

##

Step 4: Start a Verification Session
###

Option A: Verification Links (Fastest)
- In **Verifications**, click “+” and select a workflow.
- Generate a link or QR code.
- Distribute via email, SMS, or embed.

Programmatically (via API):
```
POST /v2/session/
{
"workflow_id": "your-workflow-id"
}
```
(Note: Find `workflow_id` in Console under **Verifications → Workflows**.)

###

Option B: Standalone APIs (Advanced)
Call endpoints directly from backend:
- `/v2/id-verification/`: ID document checks.
- `/v2/face-match/`: Selfie vs. ID comparison.
- `/v2/aml/`: Compliance list checks.
- `/v2/passive-liveness/`: Deepfake detection.
Suitable for custom UI or backend-only flows.

##

Step 5: Receive Real-Time Results
Didit sends webhook events (no polling needed):
- **status.updated**: On any status change, including session start.
- **data.updated**: On manual KYC/POA updates by reviewers.

##

Step 6: Manage & Monitor in the Console
- View real-time session progress.
- Export PDF/CSV reports.
- Blocklist suspicious IDs/faces.
- Manually delete/reject sessions.

##

Support
- WhatsApp: [Link](https://api.whatsapp.com/send/?phone=%2B34681310687).
- Email: support@didit.me.

#

Workflows Dashboard

Didit’s Orchestrated Workflows enable no-code design of multi-step identity verification journeys using a visual builder in the Business Console. Define logic once; Didit handles user-facing experience, state, and conditions. Ideal for onboarding, age verification, or re-authentication with minimal development.

##

Workflow Builder
Start with templates and customize features/parameters via settings.

###

Templates
| Template | Description | Starts With | Common Features |
|----------|-------------|-------------|-----------------|
| **KYC** | Onboarding for compliance. | ID Document Verification. | Liveness, Face Match 1:1, AML, NFC, PoA, Phone/Email Verification, Database Validation, Questionnaire, IP Analysis. |
| **Adaptive Age Verification** | Low-friction age gating. | Selfie-based Age Estimation. | Fallback to ID if age in buffer (e.g., 16-20 for 18+); IP Analysis. |
| **Biometric Authentication** | Re-verify returning users. | Liveness Detection. | Face Match to trusted template (pass `portrait_image` URL); Phone/Email Verification, IP Analysis. |
| **Address Verification** | Focus on PoA. | Proof of Address submission. | AI extraction/validation; Phone/Email Verification, IP Analysis. |
| **Questionnaire Verification** | Collect attestations/forms. | Questionnaire. | Configurable sections/translations/fields/uploads; IP Analysis. |

##

Customization
Adjust parameters for each feature in the builder.

##

API Integration Flow
1. Server POSTs to `/v2/session/` to create session.
2. Didit returns unique URL.
3. Redirect user to URL.
4. Didit handles steps and webhooks status to server.
5. Server updates and redirects to callback.

#

Web App Integration

Integrate Didit into web apps using redirect buttons or embedded iframes. Include `allow="camera; microphone; fullscreen; autoplay; encrypted-media"` for iframes.

##

Methods
- **Redirect Button**: User clicks to navigate to verification URL (from `/v2/session/` API).
- **Embedded Iframe**: Load verification URL inline.

Basic code examples and callback parameters are available, referencing `verification_url` from the Create Verification Session API (see Quick Start for API details).

#

Unilinks (No-Code Verification Links)

Unilinks are unique, no-code URLs for launching verifications without API integration. Suitable for simple use cases.

##

Introduction & Usage
- Create via Console for specific workflows.
- Distribute as links, QR codes, or buttons.
- Example Website Button: Embed a button linking to the Unilink.

##

When to Use
- Quick prototypes or low-volume verifications.
- Avoid for high customization or scalable integrations (use API instead).

##

Comparison with API
- Unilinks: No-code, limited options.
- API: Full control, programmatic sessions.

Best practices include secure distribution and monitoring in Console.

#

iOS & Android Integration

Embed verification natively using WebViews (no dedicated SDKs). Create session via API, load URL in WebView, handle callbacks.

##

Overview
1. Create session (get `session_url`; specify callback like `myapp://didit-callback`).
2. Open URL in configured WebView.
3. Intercept callback via custom scheme.

##

Common WebView Config
- User-Agent: Generic mobile (e.g., "Mozilla/5.0 (Linux; Android 10; Mobile) ...").
- Allow inline media playback without user gesture.
- Enable JavaScript and DOM storage (Android).

##

Code Examples
###

iOS (Swift - WKWebView)
```swift
import UIKit
import WebKit

class VerificationViewController: UIViewController, WKNavigationDelegate {
private var webView: WKWebView!

override func viewDidLoad() {
super.viewDidLoad()
setupWebView()
}

private func setupWebView() {
let configuration = WKWebViewConfiguration()
configuration.allowsInlineMediaPlayback = true
configuration.mediaTypesRequiringUserActionForPlayback = []

webView = WKWebView(frame: view.bounds, configuration: configuration)
webView.navigationDelegate = self
webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.addSubview(webView)

webView.configuration.preferences.javaScriptEnabled = true
webView.customUserAgent = "Mozilla/5.0 (Linux; Android 10; Mobile) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36"

if let url = URL(string: "{session_url}") {
webView.load(URLRequest(url: url))
}
}

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
decisionHandler(.allow)
}

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
decisionHandler(.allow)
}
}
```

Similar examples for Objective-C, Android (Java/Kotlin), React Native, and Flutter are provided in the docs, focusing on WebView setup and permissions.

##

Callback Handling
Use custom schemes; parse results and close WebView. Register in app manifests/plists.

##

Mobile UX Best Practices
| Practice | Reason |
|----------|--------|
| Full-screen WebView | Prevents accidental exits. |
| Loading spinner | Improves perception. |
| Disable pull-to-refresh | Avoids navigation issues. |
| Session timeout | Handles incomplete flows. |
| Network check | Prevents failures. |
| Real-device testing | Simulator differences. |
| Back button handling | Cancel or return gracefully. |

Demo repos: Expo (React Native), Flutter.

#

Webhooks

Configure webhooks for real-time session updates.

##

Introduction
Receive notifications on status changes. Events:
| Event | Trigger |
|-------|---------|
| **status.updated** | Any status change (includes initial start). |
| **data.updated** | Manual KYC/POA updates via API. |

Payload includes `webhook_type`; final statuses add `decision` object.

##

Configuration
1. Set up team/app (Quick Start steps 1-2).
2. In Console (**Verification Settings**): Add HTTPS endpoint, copy Webhook Secret Key.
- Cloudflare: Whitelist IP `18.203.201.92`.

##

Payload Structure
| Field | Description |
|-------|-------------|
| `session_id` | UUID of session. |
| `status` | Current status (e.g., In Progress). |
| `webhook_type` | Event type. |
| `created_at` | Event timestamp (Unix). |
| `timestamp` | Delivery timestamp (Unix). |
| `workflow_id` | Workflow UUID (optional). |
| `vendor_data` | Internal ID (optional). |
| `metadata` | Attached JSON (optional). |

Final payloads include `decision` with details like features, images, warnings.

##

HMAC Validation
Headers: `X-Signature` (HMAC-SHA256 of raw body), `X-Timestamp`.
Validate: Check existence, timestamp (±5 min), compute HMAC, constant-time compare.

##

Retry Policy
- Retry on 5xx/404 with exponential backoff (1 min, then 4 min).
- Abandons after two failures; logs in dashboard.

##

Language Examples
Full code for Node.js (Express), Python (FastAPI), PHP (Laravel) including validation and DB upserts.

##

Example Payloads
- Non-final (e.g., In Progress): Basic fields.
- Final (e.g., Declined): Adds detailed `decision` with images, warnings, etc.

#

Verification Statuses

Manage KYC with these statuses and reasons.

| Status | Description |
|--------------|-------------|
| Not Started | Process not initiated. |
| In Progress | User submitting info. |
| Approved | All checks passed. |
| Declined | Issues with submissions. |
| Kyc Expired | Renewal needed after expiration. |
| In Review | Manual review required. |
| Expired | Session timed out. |
| Abandoned | Incomplete within timeframe. |

Decision reasoning ties to descriptions (e.g., Declined due to non-meeting requirements).

#

Rate Limiting

Multi-layer limits for API stability, allowing bursts.

##

Global Limits
- GET: 300/min per app.
- Write (POST/PATCH/DELETE): 300/min per app.
- 429 responses include headers: `X-RateLimit-Limit`, `-Remaining`, `-Reset`, `Retry-After`.

##

Endpoint-Specific
| Scope | Endpoint(s) | Limit | Notes |
|------------------------|--------------------------------------|-------|-------|
| session-v2-create:free | POST /v2/session/ | 5/min | For free workflows. |
| session-v2-create:paid | POST /v2/session/ | 600/min | For paid. |
| session-decision | GET /v2/session//decision/ etc. | 100/min | Prevents polling abuse. |
| session-generate-pdf | GET /session//generate-pdf/ | 100/min | CPU-intensive. |

##

Best Practices
- Throttle on low remaining.
- Exponential backoff on 429s.
- Log retries.
Contact support for overrides.

#

Pricing

Prepaid USD credits (no expiry, no fees/minimums). Deducted only on successful verifications. Core KYC free forever.

##

Free Services
Unlimited: ID Verification (220+ countries), NFC, Document Monitoring, Reusable KYC, IP Analysis, Face Match 1:1, Passive Liveness.

##

Premium Workflow Features
Sum of enabled features per session.

| Feature | Description | Price |
|----------------------|-------------|-------|
| Active Liveness | Enhanced spoof prevention. | $0.15 |
| Biometric Authentication | Re-verify users. | $0.10 |
| Phone Verification | Fraud prevention. | $0.10 |
| Email Verification | Risk detection. | $0.03 |
| Age Estimation | Facial + ID fallback. | $0.10 |
| AML Screening | Watchlists/PEP. | $0.35 |
| AML Monitoring | Ongoing. | $0.07/year |
| Proof of Address | Document validation. | $0.50 |
| Questionnaire | Custom forms. | $0.10 |
| Database Validation | Government checks. | Varies by country |
| Whitelabel | Branding. | $0.30 |

##

Database Validation (Workflows)
Per successful match; 1x1/2x2 matching.

| Country | 1x1 | 2x2 |
|------------------|-----|-----|
| Argentina | $0.20 | N/A |
| Bolivia | $0.20 | N/A |
| Brazil | $0.20 | N/A |
| Chile | $0.20 | N/A |
| Colombia | $0.20 | N/A |
| Costa Rica | $0.20 | N/A |
| Dominican Republic | $0.05 | N/A |
| Ecuador | $0.20 | $0.30 |
| El Salvador | $0.20 | N/A |
| Guatemala | $0.20 | N/A |
| Honduras | $0.20 | N/A |
| Mexico | $0.20 | N/A |
| Panama | $0.20 | N/A |
| Paraguay | $0.20 | N/A |
| Peru | $0.20 | $0.30 |
| Spain | $0.20 | N/A |
| Uruguay | $0.20 | N/A |
| Venezuela | $0.20 | N/A |

##

Standalone API Pricing
Per successful call.

| API | Description | Price |
|----------------------|-------------|-------|
| Face Match | 1:1 comparison. | $0.05 |
| Face Search | Database search. | $0.05 |
| Age Estimation | Facial. | $0.10 |
| ID Verification | Documents. | $0.20 |
| Proof of Address | Residential. | $0.50 |
| AML Screening | Watchlists. | $0.35 |
| Passive Liveness | Spoof detection. | $0.05 |
| Phone Verification | Fraud check. | $0.10 |
| Email Verification | Risk check. | $0.03 |
| Database Validation | Authoritative. | Varies |

##

Business Model
- Prepaid credits: Buy packages; bonuses for volume (e.g., $68k purchase = $100k value, 32% discount).
- Free Plan: Unlimited core KYC.
- Enterprise: Dedicated support, custom SLAs.
- No contracts/fees; charges only on finished sessions.

Complete Guide to Integrate Mollie Payment Gateway into Your Custom Website

This guide explains how to integrate **Mollie** using their **Hosted Checkout** method (the simplest and most common approach).

#

Table of Contents

- [Prerequisites](#prerequisites)
- [1. Create and Verify Mollie Account](#1-create-and-verify-mollie-account)
- [2. Obtain API Keys](#2-obtain-api-keys)
- [3. Choose Your Integration Type](#3-choose-your-integration-type)
- [4. Install Official Mollie Client Library](#4-install-official-mollie-client-library)
- [5. Create a Payment](#5-create-a-payment)
- [6. Redirect Customer to Mollie Checkout](#6-redirect-customer-to-mollie-checkout)
- [7. Handle Customer Return (Redirect URL)](#7-handle-customer-return-redirect-url)
- [8. Set Up Webhook (Critical)](#8-set-up-webhook-critical)
- [9. Testing Your Integration](#9-testing-your-integration)
- [10. Going Live](#10-going-live)
- [11. Best Practices & Security](#11-best-practices--security)
- [12. Official Documentation Links](#12-official-documentation-links)

#

Prerequisites

- A live website (with products/prices visible) – required for Mollie approval
- Valid business bank account (IBAN)
- Server-side programming language (PHP, Node.js, Python, etc.)
- Publicly accessible HTTPS server for webhooks

#

1. Create and Verify Mollie Account

1. Sign up at [https://www.mollie.com](https://www.mollie.com)
2. Complete the **onboarding/verification**:
- Business details
- Website URL (must be live)
- Bank account
- ID documents for legal representatives
3. Wait for approval (usually 1–10 business days)

#

2. Obtain API Keys

1. Log in to your Mollie Dashboard
2. Go to **Developers → API keys**
3. Copy:
- **Test API key** (starts with `test_`)
- **Live API key** (starts with `live_`)
4. **Never expose these keys in frontend code.**

#

3. Choose Your Integration Type

| Type | Description | Recommended for beginners? |
|-----------------------|--------------------------------------------|----------------------------|
| Hosted Checkout | Redirect to Mollie’s payment page | Yes (this guide) |
| Mollie Components | Embed card form on your site (PCI) | Advanced |
| Orders API | Better refunds/shipping support | Medium |
| Subscriptions | Recurring payments | Advanced |

This guide covers **Hosted Checkout**.

#

4. Install Official Mollie Client Library

| Language | Installation Command | GitHub |
|----------|--------------------------------------------------------|--------|
| PHP | `composer require mollie/mollie-api-php` | [link](https://github.com/mollie/mollie-api-php) |
| Node.js | `npm install @mollie/api` | [link](https://github.com/mollie/mollie-api-node) |
| Python | `pip install mollie-api-python` | [link](https://github.com/mollie/mollie-api-python) |
| Ruby | `gem install mollie-api-ruby` | [link](https://github.com/mollie/mollie-api-ruby) |

#

5. Create a Payment

**Endpoint**: `POST https://api.mollie.com/v2/payments`

**Minimal payload** (JSON):

```json
{
"amount": {
"currency": "EUR",
"value": "25.00"
},
"description": "Order #12345",
"redirectUrl": "https://your-site.com/order/12345/return",
"webhookUrl": "https://your-site.com/webhook/mollie",
"locale": "en_US"
}
```

##

PHP Example (using official library)

```php
require 'vendor/autoload.php';

use Mollie\Api\MollieApiClient;

$mollie = new MollieApiClient();
$mollie->setApiKey("test_dH...your_test_key_here");

try {
$payment = $mollie->payments->create([
"amount" => [
"currency" => "EUR",
"value" => "25.00",
],
"description" => "Order #12345",
"redirectUrl" => "https://your-site.com/order/12345/return",
"webhookUrl" => "https://your-site.com/webhook/mollie",
"locale" => "en_US",
]);

// Redirect customer to Mollie checkout
header("Location: " . $payment->getCheckoutUrl());
exit;
} catch (\Mollie\Api\Exceptions\ApiException $e) {
echo "Error: " . htmlspecialchars($e->getMessage());
}
```

#

6. Redirect Customer to Mollie Checkout

After creating the payment, redirect the customer to:

```php
header("Location: " . $payment->getCheckoutUrl());
```

This takes them to Mollie’s secure hosted payment page.

#

7. Handle Customer Return (Redirect URL)

Mollie redirects back to your `redirectUrl` (e.g. `https://your-site.com/order/12345/return?payment_id=tr_abc123`).

##

PHP Example

```php
require 'vendor/autoload.php';

use Mollie\Api\MollieApiClient;

$mollie = new MollieApiClient();
$mollie->setApiKey("test_dH...your_key");

$paymentId = $_GET['payment_id'] ?? null;

if ($paymentId) {
$payment = $mollie->payments->get($paymentId);

if ($payment->isPaid()) {
echo "Payment successful! Order confirmed.";
// Update order status, send email, etc.
} elseif ($payment->isFailed() || $payment->isCancelled()) {
echo "Payment failed or cancelled.";
} else {
echo "Payment status: " . $payment->status;
}
}
```

**Important**: Do **not** rely solely on this redirect to confirm payment.

#

8. Set Up Webhook (Critical)

Mollie sends POST requests to your `webhookUrl` for every status change.

**Payload**:
```json
{ "id": "tr_abc123" }
```

##

PHP Webhook Example

```php
require 'vendor/autoload.php';

use Mollie\Api\MollieApiClient;

$mollie = new MollieApiClient();
$mollie->setApiKey("test_dH...your_key");

$paymentId = $_POST['id'] ?? null;

if ($paymentId) {
$payment = $mollie->payments->get($paymentId);

if ($payment->isPaid()) {
// Update order to "paid"
// Send confirmation email
} elseif ($payment->isRefunded()) {
// Handle refund
} elseif ($payment->isCancelled()) {
// Handle cancellation
}
}

// Always return 200 OK
http_response_code(200);
```

#

9. Testing Your Integration

- Use **test API key**
- Use Mollie’s test payment methods: [Testing Guide](https://docs.mollie.com/reference/testing)
- Test all outcomes: paid, failed, cancelled, refunded

#

10. Going Live

1. Switch to **live API key**
2. Perform a small real test transaction
3. Confirm webhook works in live mode

#

11. Best Practices & Security

- **Never expose API keys** on the frontend
- **Always use HTTPS**
- **Validate webhook origin** (optional: IP check)
- **Handle all API errors** and log them
- **Use Orders API** for complex flows (refunds, shipping)
- **Support multiple languages/currencies** via `locale`

#

12. Official Documentation Links

- [Getting Started](https://docs.mollie.com/docs/getting-started)
- [Payments API](https://docs.mollie.com/reference/v2/payments-api/create-payment)
- [Webhooks](https://docs.mollie.com/reference/v2/webhooks-api)
- [Testing](https://docs.mollie.com/reference/testing)
- [Client Libraries](https://docs.mollie.com/developers/packages)
- [Mollie Components (Advanced)](https://docs.mollie.com/components/overview)

Let me know your programming language/framework for more specific examples!

🚀 Antigravity Migration & Remote Sync Guide

This guide explains how to use the built-in **Push/Pull Synchronization System** to move code, data, and assets between your Local Development (Localhost) and your Production Server.

---

#

🛠 1. Initial Setup (One-Time)

To connect your Localhost to your Production server, you must establish a secure handshake.

1. **On Production Server**:
* Navigate to `Admin > System > Backup & Migration`.
* Scroll to **Remote Sync Settings**.
* Enter a **Secret Sync Token** (e.g., `MyUltraSecretKey123`).
* Click **Save Settings**.
2. **On Localhost**:
* Navigate to `Admin > System > Backup & Migration`.
* Scroll to **Remote Sync Settings**.
* **Production Server URL**: Enter your live site URL (e.g., `https://yourdomain.com`).
* **Secret Sync Token**: Enter the **exact same token** you saved on Production.
* Click **Save Settings**.

---

#

📤 2. Pushing Updates (Localhost ➔ Production)

Use this when you have finished coding or added new products locally and want them live.

1. **Create Snapshot**:
* Click **Generate New Backup** and select **Full System**.
* This packages your `app`, `resources`, `routes`, `database` (JSON), and `uploads`.
2. **Push**:
* Locate the completed backup in the table.
* Click the **Green Cloud Icon (Push to Production)**.
3. **Automatic Deployment**:
* The system will transmit the package via API.
* The production server will automatically extract the code and merge the database records using the **Zero-Downtime Engine**.

---

#

📥 3. Pulling Data (Production ➔ Localhost)

Use this when you want to sync your local environment with the latest live customer data.

1. **On Production**: Generate a **Logical Backup** (Recommended for data-only pull).
2. **Download**: Click the download icon to save the ZIP.
3. **On Localhost**:
* In the **Migration Import** section, upload the ZIP.
* Click the **Blue Restore Icon** to sync your local database with live data.

---

#

🛡 System Integrity Rules (Laws of the Project)

To keep the Antigravity Sync system alive and prevent "Broken State" errors, always follow these rules:

1. **Migration-First Schema**: Never change database tables manually via phpMyAdmin. Always use `php artisan make:migration`. The sync system relies on consistent schema versions.
2. **No Core Tampering**: Avoid modifying `App\Services\SystemBackupService.php` unless you are updating the manifest logic. This is the heart of the deployment engine.
3. **Environment Variables**: The `.env` file is NOT synced for security. If you add new services (e.g., Stripe, Mailgun), manually update the `.env` on both environments.
4. **Vendor Policy**: Never include the `vendor` or `node_modules` folders in manual ZIPs. The system handles core logic; dependencies should be managed via `composer install`.
5. **Clean Uploads**: Keep the `public/uploads` directory organized. The sync engine packages this folder to ensure product images never break during migration.
6. **Token Rotation**: If you suspect a security breach, change the **Secret Sync Token** on both Production and Localhost immediately to kill all active API sync bridges.

---

**System Status**: 🟢 Operational | **Version**: 2.0.0 (API-Driven)