Documentation
Everything you need to set up and manage access for your organization.
Getting Started
Get your organization up and running in under 15 minutes:
- Sign up — Create an account with your email and a password
- Create your organization — Pick a name and URL slug (e.g.
north-street-gym) - Add a location — Name it (e.g. "Main Entrance"), set a lock code that matches your physical keypad
- Print the QR code — Open the location and go to the QR & signage tab, download PNG/SVG, print and stick on the door
- Test it — Scan the QR code with your phone, enter your number, use code
123456in dev mode
That's it. Visitors can now scan and get access.
Visitor Flow
This is what a visitor experiences when they arrive at your door:
Scan QR
Phone camera opens the access page
Enter Phone
Any phone number, any country
Verify OTP
6-digit code sent via SMS
Get Code
Door code displayed, or door unlocks
No app download required. No account needed. Works on any phone with a browser and SMS.
At locations set to "Trust code required", first-time visitors are also asked for a referral code (e.g. TRUST-7X2K4M) before the OTP is sent — see Walk-in Trust & Referrals.
If the visitor's code doesn't work, they can tap "Code didn't work?" to report the issue. You'll get an email and see it in your dashboard.
Dashboard Guide
Your dashboard has these sections:
- Overview — Today's stats (access attempts, success rate, denials), pending keypad updates, visitor reports
- Locations — Add/edit/delete access points. Each location has its own QR code, lock code, and settings
- Members — Manage who has recognized access. Add individually or import via CSV
- Integrations — API keys, outbound webhooks
- Access log — Full log with filter chips, a live view, location and date-range filters, and CSV export
- Billing — Current plan, usage, and upgrade options
- Settings — Org name/slug, help & support, danger zone
Locations & QR Codes
A location represents a physical door or access point. Each location has:
- Name — e.g. "Main Entrance", "Side Door"
- Slug — URL-safe name used in the QR link:
/access/your-org/main-entrance - Lock Code — The code visitors receive. Must match your physical keypad.
- Access Mode — Open (anyone), Members Only, or Hybrid
- Walk-in Trust — how first-time visitors are treated (see Walk-in Trust & Referrals)
- Lock Tier — Keypad Code, Smart Lock (Seam), or Custom Webhook
QR Codes
Each location gets a unique QR code. Open the location and go to the QR & signage tab to:
- Download as PNG or SVG
- Copy the access URL
- Print and stick on or near the door
The QR encodes: https://yourapp.com/access/your-org-slug/location-slug
Members
Members are people your organization recognizes. They matter when a location is set to Members Only or Hybrid mode.
Adding Members
- Individual — Click "+ Add member" to open the side panel, then enter phone (required), name and email (optional)
- CSV Import — Upload a CSV with columns:
phone,name,email. Thephonecolumn is required.
Access Modes
- Open — Anyone can access. Members are tracked but not required.
- Members Only — Only phones in the members list can get a code. Others are denied.
- Hybrid — Anyone can access, but member status is logged for reporting.
Walk-in Trust & Referrals
Beyond the member list, each location can gate walk-ins(visitors who aren't members) with a trust requirement. (For a scenario-by-scenario overview of every option, see Access Options.) Set it in the location's Rules tab, under Walk-in trust:
- Everyone — any phone-verified visitor can request access (default)
- Returning visitors — walk-ins need at least one prior successful visit at a location in your community context. First-timers are denied.
- Trust code required — first-timers must enter a trust code your organization issued. Returning trusted visitors get in without one.
- Ask me each time — every walk-in waits at the door while you approve or deny live, from a card on your dashboard Overview (you also get an email). Visitors verify their phone first, and requests expire after 5 minutes. Approved first-timers become recognized returning visitors.
Members always get in (on by default) lets active members skip the walk-in gate entirely. Turn it off to send members through the same trust check as everyone else.
How trust is earned
Every successful visit records trust for that phone number — scoped to your community context(community spaces, fitness, storage, residential). Trust earned at community spaces never grants access at gyms, and vice versa — by design, with no override. Trust goes dormant after 12 months of inactivity in a context and can't be revived just by showing up (protects against recycled phone numbers).
Trust Codes
Issue codes from the Trust codes card on the Members page. Share a code (e.g. TRUST-7X2K4M) to vouch for someone:
- The visitor enters it on the access page at a "Trust code required" location
- After they verify by OTP, they're trusted in your community context going forward
- Every use is recorded against your organization and you get an email each time your code is used
- Disable a code anytime — existing trust it granted stays, but it can't admit anyone new
- If someone you vouched for is later blocked by 3+ organizations, you'll get a private email suggesting you review the code. Nothing is automatic — the decision stays yours.
Blocked Numbers
Block a number from the Blocked numberscard on the Members page. Blocked numbers are denied at every location, at every trust level, even if they're members. The visitor sees only "Access restricted" — their block status is never disclosed. Only the last 4 digits are stored for display; the full number is never kept.
Denials show in the Events log as Denied (Blocked) and Denied (First Visit)so you can see exactly why someone didn't get in.
Time-Bound Access
Give members temporary access with automatic start and end dates. No manual cleanup needed.
Use Cases
- Visiting members — "Ahmed is visiting for Ramadan (March 1-30)"
- Trial passes — "Sarah has a 30-day gym pass"
- Seasonal access — "Summer volunteers have access June-August"
- Short-term rentals — "Guest checks in Friday, out Sunday"
How It Works
When adding or editing a member, set optional date fields:
- Access From — when their access starts. Before this date, they see "Your access begins on [date]"
- Access Until — when their access expires. After this date, they see "Your access expired on [date]"
- Note — admin label for context (e.g. "Ramadan visitor", "30-day pass")
Leave both dates blank for permanent access (default behavior). The system automatically enforces the window — no manual deactivation needed.
Member Status Badges
- Active — permanent or within their access window
- Temporary — has an access window set
- Upcoming — access hasn't started yet
- Expired — access window has passed
Time Gating
Restrict access to specific hours and days. When enabled, visitors outside the window see "Access is not available at this time."
Setting Up
- Open the location's Rules tab and turn on "Limit to these windows"
- Click "+ Add a window" to add an access window
- Set open/close times and select active days
- Add multiple windows if needed (e.g. morning + evening)
Lock Codes & Rotation
The lock code is what visitors receive after verification. It must match the code on your physical keypad.
Setting a Code
In the location's Door code tab you can type any code directly or generate a new one.
Code Generation Rules
Configure how random codes are generated:
- Length — 1 to 20 characters
- Character set — Digits only, letters only, or both
- Prefix/Suffix — Code always starts or ends with specific characters
Rotation Policy
- Manual — You rotate when you want
- Daily — Auto-rotates at midnight
- Weekly — Auto-rotates every Monday
- Monthly — Auto-rotates on the 1st
- Custom — Set your own interval
Important: When the code rotates (manually or automatically), you must also update your physical keypad to match. You'll get a reminder on your dashboard until you confirm.
Smart Locks
Three unlock methods are available:
Keypad Code (default)
Visitor receives the code in the app. They type it into your physical keypad.
Smart Lock (Seam)
The door unlocks automatically via the Seam API. Supports 50+ lock brands (August, Schlage, Yale, etc.).
- Get a Seam API key at
seam.co - Add your
SEAM_API_KEYto the API environment - In the location's Lock tab, select "Smart Lock (Seam)"
- Enter your device ID
Fail-safe: If the smart lock fails, the visitor automatically receives the keypad code instead.
Custom Webhook
Send a POST request to your own endpoint when access is granted.
- In the location's Lock tab, select "Custom Webhook"
- Enter your endpoint URL
- Click "Test" to verify connectivity
The payload is HMAC-signed with that door's signing secret (Lock tab → Signing secret). Verify X-QRGate-Signature before opening anything — otherwise anyone who learns the URL could open the door. Fail-safe: If the webhook fails, the visitor receives the keypad code.
White-Label Branding
Customize how the visitor access page looks for your organization. Available on GROWTH plan and above.
Customizable Fields
- Display Name — shown on the visitor page instead of your org name (e.g. "North Street Gym")
- Logo — square image URL, displayed above the title
- Primary Color — hex color code for buttons and accents (e.g.
#16a34afor green) - Welcome Text — custom message below the title (e.g. "Please verify your phone to receive the door code.")
Setup
- Go to Dashboard → Settings → Visitor page
- Fill in any fields you want to customize (all are optional)
- Click "Save"
- Visit your access page to see the changes
All branding is optional. Without it, visitors see the standard QR Gate look.
API Keys
API keys let your external software interact with your QR Gate data. Requires GROWTH plan or above.
Creating a Key
- Go to Dashboard → Integrations and find the API keys section
- Click "Create Key", enter a label
- Copy the key immediately — it's shown only once
Using the Key
Include it in the X-API-Key header:
curl -H "X-API-Key: lmi_your_key_here" \ https://api.yourapp.com/api/v1/orgs/:orgId/members
Revoking
Click "Revoke" next to any key. It stops working immediately. This cannot be undone.
Outbound Webhooks
Receive real-time HTTP notifications when events happen in your organization.
Setting Up
- Go to Integrations and click "+ Add webhook" in the Webhooks section
- Enter your endpoint URL
- Select which events to subscribe to
- Copy the signing secret — shown only once
Available Events
access.granted— A visitor was granted accessaccess.denied— A visitor was denied access (with the reason inresult)code.rotated— A lock code was changed (the new code is never sent)
Every payload includes a plain-English text line, so a Slack incoming-webhook URL works as-is.
Payload Format
POST https://your-endpoint.com/webhook
Header: X-QRGate-Signature: sha256=<hmac>
Header: X-QRGate-Delivery: <delivery id — the same on every retry>
Content-Type: application/json
{
"event": "access.denied",
"orgId": "clx...",
"locationId": "clx...",
"locationName": "Main Entrance",
"phonePartial": "**7823",
"result": "DENIED_NOT_MEMBER",
"timestamp": "2026-03-24T14:32:00Z",
"text": "Access denied at Main Entrance · **7823 · not on the members list"
}Retries
Reply with any 2xxwithin 10 seconds. Anything else (including redirects, which aren't followed) is retried after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h — about a day in all. Use X-QRGate-Deliveryto ignore a delivery you've already processed. Integrations shows the latest delivery result for each webhook.
Verifying Signatures
Verify the X-QRGate-Signature header against the raw request body (not re-serialized JSON) using your signing secret:
const crypto = require('crypto');
// rawBody: the exact bytes received, e.g. express.raw({ type: 'application/json' })
const expected = 'sha256=' + crypto
.createHmac('sha256', YOUR_WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
const received = req.headers['x-qrgate-signature'] ?? '';
const valid = received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!valid) return res.status(401).send('Invalid signature');API Reference
All API routes are prefixed with /api/v1/. Authenticate with X-API-Key header.
Members
GET /api/v1/orgs/:orgId/members # List all members
POST /api/v1/orgs/:orgId/members # Add a member { phone, name?, email? }
POST /api/v1/orgs/:orgId/members/bulk # CSV import { members: [...] }
PATCH /api/v1/members/:id/toggle # Toggle active/inactive
DELETE /api/v1/orgs/:orgId/members/:id # Remove memberTrust Codes & Blocklist
GET /api/v1/orgs/:orgId/trust-codes # List codes with usage counts
POST /api/v1/orgs/:orgId/trust-codes # Issue a code { label? }
PATCH /api/v1/orgs/:orgId/trust-codes/:id/toggle # Enable/disable
GET /api/v1/orgs/:orgId/blocklist # List blocked numbers (last 4 only)
POST /api/v1/orgs/:orgId/blocklist # Block { phone }
DELETE /api/v1/orgs/:orgId/blocklist/:id # UnblockLocations
GET /api/v1/orgs/:orgId/locations # List locations
POST /api/v1/orgs/:orgId/locations # Create { name, slug, lockCode?, ... }
GET /api/v1/locations/:id # Get location details
PATCH /api/v1/locations/:id # Update location
DELETE /api/v1/locations/:id # Delete location
POST /api/v1/locations/:id/rotate-code # Rotate lock code { code?, rules? }Access Events
GET /api/v1/orgs/:orgId/events ?locationId= # Filter by location &result= # GRANTED, DENIED_OTP, DENIED_HOURS, etc. &from= # ISO date start &to= # ISO date end &page= # Page number &limit= # Items per page (max 100)
Webhooks
GET /api/v1/orgs/:orgId/webhooks # List webhooks
POST /api/v1/orgs/:orgId/webhooks # Create { url, events[] }
PATCH /api/v1/webhooks/:id/toggle # Pause/resume
DELETE /api/v1/orgs/:orgId/webhooks/:id # DeleteBilling & Plans
| Plan | Price | Locations | Events/mo | Smart Lock | API |
|---|---|---|---|---|---|
| FREE | $0 | 1 | 50 | — | — |
| STARTER | $12/mo | 3 | 500 | ✓ | — |
| GROWTH | $29/mo | 10 | 2,000 | ✓ | ✓ |
| PRO | $79/mo | ∞ | ∞ | ✓ | ✓ |
| ENTERPRISE | Custom | ∞ | ∞ | ✓ | ✓ |
Upgrade anytime from Dashboard → Billing. Downgrades take effect at the end of your billing period.
Security
- Phone numbers are never stored raw in access logs — only SHA-256 hashes and last 4 digits for display
- Lock codes encrypted at rest — AES-256-GCM encryption, decrypted only when shown to visitors
- API keys are hashed before storage — SHA-256, raw key shown once on creation
- Webhook secrets are shown once — on creation only; we keep them to sign deliveries
- All outbound webhooks are HMAC-signed — verify the
X-QRGate-Signatureheader - Session tokens are single-use — 10-minute expiry JWTs, invalidated after first verification
- CAPTCHA protection — Cloudflare Turnstile on the visitor phone form prevents bot abuse
- Rate limiting — 3 OTP requests per phone per hour, 60 API requests per minute per IP
- Stripe webhook verification — signature check prevents forged billing events
- Checkout rate limiting — 3 attempts per org per hour prevents card testing
- Row-Level Security — PostgreSQL RLS policies prevent cross-org data access
- Security headers — CSP, HSTS, X-Frame-Options, helmet on API
- Cloudflare protection — DDoS mitigation, bot detection, WAF at the edge
- Admin passwords are bcrypt-hashed — sessions are short-lived signed cookies, never stored server-side
- Fail-safe design — if smart lock or webhook fails, visitors get the keypad code instead
Help & Support
Get help directly from the dashboard. Go to Settings → Help & Support to find:
- Report a Bug — select a category (visitor flow, dashboard, lock codes, members, QR codes), describe the issue, and optionally add reproduction steps. We receive an email immediately.
- Request a Feature — tell us what problem it would solve and how you'd like it to work. Feature requests help us prioritize development.
- Contact Support — for questions, account issues, or anything else. Include your email and we'll respond as soon as possible.
All submissions are tracked in our internal system. We review every ticket.
Visitor Feedback
When a visitor reports an issue (e.g. "Code didn't work"), you'll:
- Receive an email notification immediately
- See it on your Dashboard overview as a red banner
- Find the reports in the location's Door code tab
- Resolve individual reports with one click
FAQ
Do visitors need to download an app?
No. The entire flow works in the browser. Scan QR → enter phone → get code.
What if a visitor's code doesn't work?
They can tap 'Code didn't work?' on the access granted screen. You'll get an email and see the report in your dashboard.
What happens if the smart lock fails?
The system automatically falls back to sending the keypad code via the app. Fail-safe by design.
Can I use this without a smart lock?
Yes. The default mode is Keypad Code — visitors get the code, type it into your physical keypad.
How do I restrict access to members only?
Set the location's Access Mode to 'Members Only'. Only phones in your members list will be allowed through.
Can I set different access hours for different days?
Yes. Time gating supports multiple windows with per-day selection.
Is my data shared between organizations?
No. Every query is scoped to your organization. Database-level Row-Level Security prevents cross-org access.
Can I export my data?
Yes. Members and access events can be exported as CSV from the dashboard.
Can I give temporary access to visitors?
Yes. Set 'Access From' and 'Access Until' dates on a member. Their access is automatically enforced — no manual deactivation needed.
How do I customize the visitor page with my branding?
Go to Settings → Visitor page (GROWTH plan and above). Add your logo, primary color, display name, and welcome text.
How do I report a problem?
Go to Dashboard → Settings → Help & Support. You can report bugs, request features, or contact support directly.