SnagBane — Standalone forms for Microsoft 365 & Google
Standalone: SnagBane runs on your own server and replaces Microsoft Forms and Google Forms without depending on them — forms, answers and files stay in your database. The Free plan (up to 25 people, no licence) is the complete app — builder with branching and calculations, portals, approvals, workspaces, email, webhooks, Make / n8n and many integrations — and needs no Microsoft or Google account. Business connects Microsoft 365, Google Workspace, Okta and SAML (sign-in, live directory person pickers and prefill, groups, Teams apps, SharePoint, Excel, Google Sheets and Drive, Outlook / Gmail mail) and adds every integration, Power Automate / Zapier, the REST API, payments and the phone app. Enterprise adds the security package. Sections marked (Business) or (Enterprise plan) or about Microsoft 365, Google Workspace and Okta need a licence — see Plans & licence.
Requirements
| Component | Minimum |
|---|---|
| PHP | 8.3 or newer — with the October 2026 security fixes: 8.3.36, 8.4.27 or 8.5.12 or newer (how to update) — with extensions: pdo, mbstring, openssl, tokenizer, xml, ctype, json, fileinfo, curl; recommended: dom and gd (PDFs of responses) |
| Database | MySQL 8 / MariaDB 10.6+ (recommended), PostgreSQL 14+, or SQLite 3.35+ |
| Web server | Apache (with mod_rewrite) or Nginx, with HTTPS (required by Microsoft for sign-in redirect URIs other than localhost) |
| Outbound HTTPS | To login.microsoftonline.com and graph.microsoft.com for Microsoft 365 features |
The download includes vendor/ and pre-built front-end assets. You don't need Composer or Node.js.
Install on cPanel / shared hosting
- Create a MySQL database and user in cPanel → Databases → Manage My Databases (cPanel 118 and earlier: MySQL® Databases): under Create New Database create the database; under Add New User create a user (use Password Generator and copy the password); under Add User To Database add the user with ALL PRIVILEGES. cPanel puts your account name in front of both names (e.g.
youraccount_snagbane). The installer shows these steps too. To change the password later, change it in cPanel (Current Users → Change Password) and put the same password inDB_PASSWORDin.env, or runphp artisan snagbane:db-password, which changes both. - Upload
snagbane.zipwith File Manager and extract it, for example into/home/you/snagbane. - Point your (sub)domain's document root to
/home/you/snagbane/public. If your host doesn't allow that, move the contents ofpublic/intopublic_html/and fix the two paths inpublic_html/index.phpto point at../snagbane/. - Open
https://your-domain/installand follow the three steps: requirements, database, admin account. - Add one cron job in cPanel → Cron Jobs (every minute):
cd /home/you/snagbane && php artisan schedule:run >/dev/null 2>&1— approval reminders, the nightly directory copy for fast person search and clean-up run from it. - Sign in and go to Settings → Microsoft 365.
Install on a VPS (Ubuntu + Nginx)
sudo apt install nginx php8.3-fpm php8.3-{mysql,mbstring,xml,curl,zip,intl,sqlite3} mariadb-server unzip
sudo unzip snagbane.zip -d /var/www/snagbane && sudo chown -R www-data: /var/www/snagbane
# nginx: root /var/www/snagbane/public; try_files $uri $uri/ /index.php?$query_string;
sudo certbot --nginx -d forms.example.com
echo '* * * * * www-data cd /var/www/snagbane && php artisan schedule:run >/dev/null 2>&1' | sudo tee /etc/cron.d/snagbane
Then browse to /install.
Install with Docker (recommended)
The stack runs SnagBane (Apache + PHP 8.3), a background worker for emails / webhooks / integrations, a scheduler (approval reminders, clean-up), MariaDB and Mailpit for testing email.
docker compose up -d --build
# open http://localhost:8080/install
# Database: MariaDB · host db · database snagbane · user snagbane · leave the password empty
# test inbox for all outgoing email: http://localhost:8025 (in Settings → Email use SMTP host "mailpit", port 1025, no encryption)
No default database passwords. During installation SnagBane replaces the database's starting password with a strong random password for this installation and keeps it in storage/.env (in the storage volume). MariaDB's root password is random too (SnagBane doesn't need it; it is printed once: docker compose logs db | grep "GENERATED ROOT PASSWORD"). See the password with docker compose exec app php artisan snagbane:db-password --show; change it with docker compose exec app php artisan snagbane:db-password (random, or --password=…), then docker compose restart.
Set SNAGBANE_PORT (default 8080) and SNAGBANE_MAIL_PORT (Mailpit, default 8025) in a .env file next to docker-compose.yml if those ports are taken, e.g. by a second copy of SnagBane. Everything that must survive upgrades lives in the storage and dbdata volumes; the exports/ folder receives Local folder exports. Upgrading: replace the files, then docker compose up -d --build — migrations run automatically.
Run locally on Windows (for testing, demos and recording videos)
The easiest way is Laragon (free):
- Install Laragon Full, which includes PHP 8.3, MySQL and Apache.
- Copy the SnagBane folder into
C:\laragon\www\snagbane. - Start Laragon. It serves the site at
http://snagbane.test. - Open
http://snagbane.test/install, pick SQLite and tick Import demo forms and responses.
You can also double-click start-local.bat if PHP is on your PATH. It serves at http://127.0.0.1:8000.
http://localhost redirect URIs for sign-in, so SSO works locally if you open SnagBane at http://localhost:8000.PHP security update (October 2026)
On 21 October 2026 the PHP project released fixes for two critical vulnerabilities in PHP itself, which affect every PHP application. SnagBane's updater updates SnagBane, not PHP, so update PHP on the server too. Settings → System & updates shows a warning on PHP version until it is done. Ubuntu and Debian patch their own PHP packages without changing the version number (Ubuntu 24.04: php -v still says 8.3.6 once 8.3.6-0ubuntu0.24.04.13 is installed); from 2.54.9 SnagBane accepts such builds when they were made after the fix.
- Docker: the image is built on PHP 8.3.36 from SnagBane 2.54.8 on. Update SnagBane, then rebuild in the SnagBane folder:
docker compose build --pull --no-cache && docker compose up -d. Check withdocker compose exec app php -v. - VPS / your own server: update PHP with the system's packages (for example
sudo apt update && sudo apt upgrade) to 8.3.36, 8.4.27, 8.5.12 or newer, then restart PHP-FPM or Apache. - cPanel / shared hosting / Windows: ask your host to update PHP, or pick the updated version in the hosting panel (e.g. MultiPHP Manager). On Laragon or XAMPP, install the new PHP build.
Monitoring and alerts
Settings → Monitoring shows the server SnagBane runs on: CPU (with load and number of cores), memory (the container's limit when SnagBane runs in Docker) and the disk that holds storage/ (uploads, backups, the SQLite database), with history for the last hour, 24 hours or 7 days. The scheduler takes one sample a minute and keeps them 7 days. Values a host doesn't expose (Windows, some shared hosting) show Not available on this server.
Alerts: Add alert → pick CPU, memory or disk, a limit in % and for how many minutes (for example disk above 90 % for 5 minutes) and who gets the email. The email goes out when every sample of that time is above the limit, again every 6 hours while it lasts, and once more when it's back to normal; Send a test email checks the addresses. Alerts need email (Settings → Email) and the scheduler. Every alert is in Settings → Logs → System (system.alert_fired, system.alert_resolved).
Updating
One click: Settings → System & updates → Updates. SnagBane checks for a new version once a day (or click Check for updates); the Owner clicks Update now. SnagBane then:
- downloads the new version and checks its digital signature (a package that isn't signed by SnagBane is refused);
- saves a backup of the current version (Settings → System & updates → Restore previous version undoes the update);
- shows a short maintenance page, copies the new files and updates the database. Files that the new version no longer has are removed from SnagBane's own code folders (
app,config,enterprise,lang,resources,routes,vendor,public/build,public/docsanddatabase/migrations,seeders,factories), so leftovers can't break it; your own files elsewhere (for example inpublic/) stay. Your.env,storage/, uploads and data are never touched.
- Shared hosting / cPanel: works as-is; the web server needs write access to the SnagBane folder.
- Docker: the update is also kept in the storage volume and re-applied when the containers restart. When you later pull or build a newer image, the image wins.
- If the page lost the connection during the update (a proxy or load balancer timed out the long request), the site may show the maintenance page for a moment: SnagBane finishes the update by itself within a few minutes (the scheduler runs every minute). To finish it right away, run
php artisan snagbane:update --finishin the SnagBane folder (Docker:docker compose exec app php artisan snagbane:update --finish). - Command line:
php artisan snagbane:update(or--checkto only look). - Business and Enterprise updates need an active licence. When the licence ends, new versions and the paid features pause until you renew (see Plans & licence); forms, responses and approvals keep working and nothing is deleted.
- Manual: back up
.env,storage/and the database, upload the new files over the old ones (keep.envandstorage/; best replace the code folders listed above completely, so no old files are left), then runphp artisan migrate --forceor click System → Clear cache.
Connect your Microsoft 365 tenant
Go to Settings → Microsoft 365 and choose the features (sign-in is always on; tick Person picker & profile prefill, and optionally groups and mail). SnagBane needs an app registration in your own tenant. The recommended way creates it with Microsoft's own tooling under your admin account — nothing passes through a third party.
PowerShell (recommended): the PowerShell tab shows New-SnagBaneApp.ps1 for the features you ticked (copy or download it so your security team can review it). Run it in Windows PowerShell or PowerShell 7 as a Global Administrator (or Application Administrator + Privileged Role Administrator). It needs the Microsoft.Graph module (Install-Module Microsoft.Graph -Scope CurrentUser), signs you in with your normal browser (MFA, Security defaults and Conditional Access work), creates the SnagBane Forms app registration, a 24-month client secret and admin consent, and prints three values. Paste them into the Manual tab and click Save & test.
Manual (Entra admin center): create an app registration yourself with the redirect URI shown in the wizard, add a client secret, add the Microsoft Graph permissions listed there and click Grant admin consent. Then paste the tenant ID, client ID and secret into SnagBane and click Save & test.
SnagBane only stores the app's tenant ID, client ID and client secret, and the secret is encrypted with your APP_KEY.
Other ways: Sign in with Microsoft
Under Other ways, SnagBane can create the app itself after a one-time admin sign-in through Microsoft's public Microsoft Graph Command Line Tools client:
- Sign in with Microsoft. A new tab opens Microsoft's normal sign-in page. Sign in as a Global Administrator (or Application Administrator + Privileged Role Administrator) and accept the permissions.
- Paste the address. The tab ends on an error page such as “This site can’t be reached” (address
http://localhost:1/?code=…) — that is expected: the browser never connects, so nothing on your computer receives the code. Click its address bar, copy the whole address (Ctrl+L, Ctrl+C; on a Mac ⌘L, ⌘C), paste it into SnagBane and click Continue. The address contains a one-time sign-in code; the full address or just the part after?both work. - Create the app. SnagBane then creates the SnagBane Forms app registration and enterprise app, adds a 24-month client secret, and grants admin consent for exactly the permissions you selected. You see each step as it completes.
The admin's sign-in token is used once and then thrown away. SnagBane only stores the new app's tenant ID, client ID and client secret, and the secret is encrypted with your APP_KEY.
How it works: the sign-in uses Microsoft's first-party Microsoft Graph Command Line Tools client with the authorization-code flow and PKCE (no client secret). That client only accepts Microsoft's registered return addresses (such as http://localhost), not your server's, which is why you copy the address instead of being sent back automatically. The code is useless without a secret that never leaves your SnagBane server, works once, expires after a few minutes and only for the administrator who started the sign-in.
Why not device code? The Device code tab (under Other ways) still exists, but Microsoft now blocks device-code sign-in in most tenants: Security defaults (on by default in new tenants) block it with error AADSTS530035, and the Microsoft-managed Conditional Access policy Block device code flow does the same (AADSTS53003). SnagBane recognises these errors and sends you to Sign in with Microsoft.
Permissions explained
| Feature | Permission | Type | Why |
|---|---|---|---|
| Sign in with Microsoft | openid, profile, email, offline_access, User.Read | Delegated | Sign users in and read their own profile |
| Person picker & prefill | User.Read.All | Application | Search the directory and read job title, department, manager and profile photo |
| Groups | GroupMember.Read.All | Application | "Specific groups" form access and mapping the admin group |
| Mail.Send | Application | Send notifications from a mailbox. Restrict this with an application access policy. | |
| SharePoint / Excel | Sites.ReadWrite.All, Sites.Manage.All | Application | Save responses to SharePoint lists (Manage: create lists and columns) and files |
| SharePoint / Excel | Files.ReadWrite.All | Delegated | Excel rows, written as the connected Excel account (see below) |
| Create and switch off accounts | User.ReadWrite.All, User.RevokeSessions.All, GroupMember.ReadWrite.All, LicenseAssignment.ReadWrite.All, LicenseAssignment.Read.All | Application | Strong permissions — tick only if you use it. The Microsoft 365 accounts integration (Enterprise plan): create accounts for new starters, switch off leavers' accounts and sign them out, groups and licences. SnagBane never deletes an account and never changes anyone with an admin role. |
| Outlook calendars | Calendars.ReadWrite | Application | Put approved requests into calendars (the Outlook calendar integration). Limit it to the mailboxes you choose with Exchange Online RBAC for Applications. |
Excel: the connected account
Microsoft's Excel (workbook) API only works for a signed-in account — it has no application permissions — so, like a Power Automate connection, an administrator connects one account under Settings → Microsoft 365 → Excel account → Connect Excel account (a normal Microsoft sign-in through your own SnagBane app registration). Use a service account (for example forms-bot@company.com) and give it Can edit on the workbooks you use. SnagBane keeps its refresh token encrypted; if the sign-in expires or is revoked (password reset, the account is disabled), the Excel integration says so and you connect again.
In a form's Integrations tab, add Excel, paste the workbook's sharing link (Share → Copy link) and a table name. If the workbook has no table with that name, SnagBane adds a new sheet named after the table and creates the table there, with a column per question, so new rows don't appear on Sheet1; an existing table is used on whatever sheet it is. With Create the table and missing columns automatically on, questions added later get a column at the end of the table. Off, nothing is created: the table must exist, answers go only into columns with the same name (upper/lower case doesn't matter) and other columns stay empty. To choose the columns yourself, use Field mapping: column name on the left, what goes in it on the right (merge tags such as {{employee}} or fixed text); with automatic creation off, a mapped column the table doesn't have fails the run with its name. Each run says on which sheet the row went. Answers that start like a formula (=, +, -, @) are written as text.
Sign in with Microsoft
Once connected, the login page shows Sign in with Microsoft. With Create accounts automatically on, anyone in your tenant gets an account with the default role when they first sign in. Pick an Admin group and its members become SnagBane admins each time they sign in.
Connect Google Workspace
Go to Settings → Google Workspace and tick the features you need. Google does not allow apps to create OAuth clients for you, so the page gives you a short checklist with every value ready to copy.
- OAuth client (needed for Sign in with Google and for Connect with Google): in Google Cloud Console create an OAuth client (Web application) with the origin and redirect URI shown, set the app's audience to Internal, paste its ID and secret. Set “Restrict to domain” to your Workspace domain.
- Choose how SnagBane connects for the directory, Gmail, Sheets, Drive and Calendar — the page lists what each way can and can't do:
| Connect with Google (recommended) | Service account (Advanced) | |
|---|---|---|
| Setup | Enable the APIs, click Connect with Google, sign in once. No key file, no domain-wide delegation, no Google Cloud policy change. | Service-account JSON key + domain-wide delegation (Admin console → Security → Access and data control → API controls → Manage Domain Wide Delegation). Cloud organisations created since May 2024 block new keys by default (Disable service account key creation); allowing them for this project needs the Organization Policy Administrator role on the organisation, which organisation admins don’t have by default — grant it to yourself first (IAM & Admin → IAM → Grant access). |
| Acts as | One account only — best a dedicated one (e.g. snagbane@company.com) with a custom admin role that may only read users and groups. It can do only what that account may do. | Any user in the domain, within the scopes you list. Keep the key safe and rotate it; it doesn't expire on its own. |
| Gmail | Sends only from the connected account. | From the mailbox you choose. |
| Calendar | Shared or team calendars the account can edit; the requester can be invited. Not each requester's own calendar. | Also each requester's own calendar. |
| Drive / Sheets | Folders and sheets the account can edit — no sharing with a robot account. | Share each sheet/folder with the service account (or upload as a user). |
| When it stops | If the account is suspended or deleted, its access is removed, or it's unused for 6 months (for Gmail also when its password changes) — the page and the health check say so; Connect again. | If the key is deleted or the delegation removed. |
Click Save & test — each part is checked and any problem is explained. SnagBane keeps a local copy of each connected directory for person search — Microsoft 365 and Google Workspace each have their own, shown on their own page. Person questions and approval steps pick their directory; where none is picked, the default one is used (Settings → Security → Default directory). With a service account, the account in Admin email to impersonate must be an admin that may read users and groups (super admin, or a custom role with Users → Read and Groups → Read); the health check row Google admin access shows what Google says about it.
Sign in with Okta
Settings → Okta. The page shows every value to copy. In the Okta Admin Console:
- Applications and Resources → Applications → Create App Integration (Classic experience) → sign-in method OIDC – OpenID Connect, application type Web Application → Next.
- Name it (e.g. SnagBane), grant type Authorization Code only, and the Sign-in redirect URI shown on the SnagBane page (
https://forms.yourcompany.com/auth/okta/callback); the sign-out redirect URI is optional. - Assignments: Limit access to selected groups (or everyone in your organisation) → Save. Only people the app is assigned to can sign in; change it later on the app's Assignments tab.
- On the General tab copy the Client ID and Client secret. In SnagBane paste the Okta domain (e.g.
company.okta.com), client ID and secret and click Save & test. Leave Authorization server empty to use the org server, or enter e.g.defaultfor a custom one. - Sign-in policy — the step that most often stops a working setup: Sign On tab → User authentication → Edit. New apps get Okta's default app sign-in policy Any two factors: everyone then needs a second factor (for example Okta Verify) enrolled, or Okta ends the sign-in before SnagBane sees it. Choose Password only, One factor access or your own policy if two factors isn't what you want; all policies are under Security → Authentication Policies. Okta's guides: App sign-in policies, Preset app sign-in policies.
- Groups claim (for group-based access and roles): Sign On tab → OpenID Connect ID Token → Edit → Groups claim type Filter, name
groups, Matches regex.*(or a narrower filter) → Save (Okta's guide). - Optional — start from the Okta dashboard: General → Login → Login initiated by “Either Okta or App”, login flow “Redirect to app to initiate login”, and the Initiate login URI from the SnagBane page.
Sign-in doesn't work? “User is not assigned to the client application” → assign the person or a group of theirs (Assignments tab). Okta asks for a factor the person doesn't have, or the sign-in ends with “access denied” → the app sign-in policy needs more than the person has set up (step 5). “Okta didn't send your groups” → step 6 (with a custom authorization server, add the groups claim on that server). A 400 / redirect URI error → the sign-in redirect URI must match exactly.
- Groups: in the Okta app's Sign On tab set the Groups claim (name
groups, filter Matches regex.*or narrower). Then Settings → Groups → New group → Okta and type the Okta group name (names seen at sign-in are suggested). Add the group to workspaces and/or give it an organisation role — members get it automatically. - Okta sends groups when people sign in, so group changes apply at their next Okta sign-in (Microsoft 365 and Google groups are checked live).
- Person pickers and profile prefill keep using Microsoft 365 or Google Workspace.
- Okta API: person search, groups and people who leave (optional, read-only): with this connection person pickers search Okta and prefill job title, department and manager (
managerIdormanagerin the Okta profile), Okta groups can be searched under Settings → Groups, their members listed and membership checked live (cached 10 minutes), and SnagBane asks Okta once a day about every account that signs in with Okta. Switch person search off on the Okta page if another directory should be used (Settings → Security → Directory chooses when several are connected). Create an API Services app integration; under General → Client Credentials choose client authentication Public key / Private key and add the public key the Okta page shows (Create the key — the private key never leaves SnagBane); on the Okta API Scopes tab grantokta.users.readandokta.groups.read(without the second, group search and member lists stay off); on the Admin roles tab assign Read-Only Administrator; paste the app's client ID and click Save & test. This is Okta's recommended “OAuth 2.0 for Okta” (no API token); when the app requires DPoP, SnagBane sends DPoP proofs. Deactivated or suspended accounts are listed as disabled, deleted ones as deleted — see People who leave. - Who can use SnagBane (Enterprise plan): Okta's own assignments decide first (only people the Okta app is assigned to can sign in). To narrow it further, pick Okta groups by name on the Okta page; others are refused at sign-in. Without a
groupsclaim the groups are read with the Okta API above; without either, people are refused (owners and admins always get in). - Job title, department and manager (optional): add custom claims named
title,departmentandmanager(a name or an email address; ormanager_email) to the ID token, each with an Okta Expression Language expression for the matching attribute of the Okta user profile (see the attribute names under Directory → Profile Editor). How to add claims: Okta's guide Customize tokens returned from Okta with custom claims. SnagBane reads them at each sign-in; claims that are only in/userinfowork too.
Report a problem
Enterprise. Settings → Report a problem sends a bug report, an idea or a question to the SnagBane team, using your server's email settings (our reply goes to you). Without email set up, open it in your email app or copy the text. Include system details lists exactly what is added (version, licence state, PHP, Laravel, database, queue, connected directories, browser) — never passwords, keys, responses or people's data.
SAML single sign-on (AD FS, Entra ID, Okta, Ping…)
Business.
Any SAML 2.0 identity provider can sign people in: Settings → SAML single sign-on. SnagBane is the service provider; the page shows the values your identity provider needs:
| SnagBane value | Where it goes |
|---|---|
Entity ID https://forms.yourcompany.com/auth/saml/metadata | Identifier / Audience / Relying party identifier |
Reply URL https://forms.yourcompany.com/auth/saml/acs | Assertion Consumer Service (ACS) / Single sign-on URL (HTTP-POST) |
| Metadata URL (same as the entity ID) | For identity providers that import it (AD FS) |
Then give SnagBane the identity provider’s side: paste its metadata URL (or XML) and click Import — or enter its entity ID, sign-in URL (HTTP-Redirect) and signing certificate by hand. Switch on Show the single sign-on button and save. Test sign-in opens a new tab.
- Who the person is: the email address, from an email attribute or the NameID. A display name (or first + last name) is used as the name. Leave Attribute names empty to use the usual names (the claim URIs of Entra ID and AD FS,
email,displayName,groups…). - Groups: the values of the groups attribute are read at every sign-in. Link them under Settings → Groups → New group → SAML (type the value exactly as the identity provider sends it — a name, or an object ID with Entra ID) to give workspace access or a role. Roles from groups only ever raise a role.
- New accounts get the default role (respondent unless you choose viewer or editor) — never administrator. Turn off Create accounts automatically to allow only people who already have an account. An existing account with the same email is linked at its first SAML sign-in; an account linked to another NameID is refused.
- Security: only sign-ins started from SnagBane are accepted (no identity-provider-initiated sign-in), the response or the assertion must be signed with one of the saved certificates (SHA-256; SHA-1 is refused), audience, destination and validity times are checked, and each assertion works once. Add a second certificate before the identity provider rolls its certificate over.
Microsoft Entra ID
- Entra admin center → Entra ID → Enterprise apps → All applications → New application → Create your own application. Name it “SnagBane”, choose Integrate any other application you don’t find in the gallery (Non-gallery), Create.
- Single sign-on → SAML → Basic SAML Configuration → Edit: Identifier (Entity ID) = SnagBane’s entity ID, Reply URL (Assertion Consumer Service URL) = SnagBane’s reply URL. Save.
- In SAML Certificates, copy the App Federation Metadata Url and import it in SnagBane.
- Optional, for groups: Attributes & Claims → Edit → Add a group claim — for example Groups assigned to the application (recommended for large organisations). Entra ID sends group object IDs; for groups synced from Active Directory you can pick
sAMAccountNameas the source attribute. - Users and groups: assign who may sign in.
AD FS
- AD FS Management → Relying Party Trusts → Add Relying Party Trust → Claims aware → Start → Import data about the relying party published online or on a local network → SnagBane’s metadata URL. Finish the wizard.
- Right-click the trust → Edit Claim Issuance Policy → Add Rule → Send LDAP Attributes as Claims, attribute store Active Directory: E-Mail-Addresses → E-Mail Address, Display-Name → Name, and for groups Token-Groups - Unqualified Names → Group.
- Add Rule → Transform an Incoming Claim: incoming E-Mail Address → outgoing Name ID (format Email).
- In SnagBane, import AD FS’s metadata:
https://fs.yourcompany.com/FederationMetadata/2007-06/FederationMetadata.xml.
Okta (SAML instead of OpenID Connect)
- Okta Admin Console → Applications → Create App Integration → SAML 2.0.
- Single sign-on URL = SnagBane’s reply URL, Audience URI (SP Entity ID) = SnagBane’s entity ID, Name ID format EmailAddress.
- Optional: Attribute Statements (e.g.
displayName) and a Group Attribute Statement namedgroupswith a filter such as Matches regex.*. - Assign people or groups, then import the app’s metadata URL (Sign On tab) in SnagBane.
Behind a proxy, SnagBane uses its configured address (APP_URL) for these URLs — make sure it is the public https address.
Adding people & invitations
Settings → Users & roles → Add user. With email set up (Settings → Email), Email them an invitation is ticked: the person gets a link to choose their own password — nobody else sees it. The link works once and for 7 days; a new invitation, a password reset or a changed email address makes the old link stop working. People who sign in with Microsoft, Google, Okta or SAML get a sign-in link instead. Until someone signs in, the list shows Invited and ⋯ → Send the invitation again. Without email (or when sending fails), SnagBane shows a generated password once for you to pass on.
Job title, department & manager
Every account has a job title, department and manager. They are shown in Settings → Users & roles (search finds them too), used for Manager approval steps and to prefill forms.
Profile photos. With Microsoft 365 or Google Workspace connected for the directory (person search), SnagBane copies each person's profile photo at sign-in and after the directory sync (at most once a week per person, 200 per sync) and shows it in the menu, the profile, Users & roles and person pickers. No extra permission is needed: Microsoft uses the directory's User.Read.All, Google its admin.directory.user.readonly. Photos are stored on your server (storage/app/private/avatars) and shown only to signed-in people; a photo removed in the directory disappears at the next check. Okta has no photo API, so Okta accounts keep their initials.
- Microsoft 365 / Google Workspace: filled from the directory — at sign-in and after each directory sync (every night, or Sync now on the Microsoft 365 / Google Workspace page). Uses the permissions the directory features already have.
- Okta: from the claims described under Sign in with Okta.
- By hand: Users & roles → ⋯ → Edit details. What you type is kept: the directory no longer changes these fields for that person until you tick Update these fields from the directory again.
- Without any directory (for example on the Free plan), Manager approval steps and prefill use what you typed here.
Sign-in methods & passwords
The sign-in page always offers email + password, plus a button for each single sign-on that is set up and switched on: Microsoft, Google, Okta. A person is linked to a provider the first time they sign in with it (matched by email).
- Settings → Security → Allow passwords for Microsoft, Google and Okta accounts (on by default). Turn it off to make linked people use their single sign-on; owners can still use a password so you can't lock yourself out if single sign-on breaks.
- Only owners can give a password to a linked account.
- With fast person search on, an account that is disabled or deleted in Entra ID (or suspended in your Google Workspace domain) can no longer sign in with a password either — from the next directory sync.
- SnagBane never changes anything in Entra ID, Google or Okta — its access there is read-only; SnagBane passwords exist only in SnagBane.
Getting around
The menu on the left has the same places for everyone (you only see what your role allows):
- Home — what waits for you, what is late and how long decisions take. Approvals — Waiting for me, My requests and Completed; the number on it counts what you can decide now.
- Forms & portals — Forms, Results (answers, summaries, export) and Portals.
- Connect — Automations & API (integrations, webhooks, Power Automate, API tokens) and the Phone app.
- Administration → Settings — everything about the organisation, people, sign-in and the system, in groups: Organisation (General & brand, Workspaces, Domains, Email, Languages), People (Users & roles, Groups), Sign-in & services (Microsoft 365, Google Workspace, Okta, SAML, Payments), Security (Security, Logs) and Plan & system (Licence & plan, System & updates). In these docs a path like Settings → Security means this area.
The top bar shows where you are, a New menu (form or portal), the approvals bell and your account menu. Press Ctrl+K (⌘K on a Mac) anywhere to jump to any screen, form or portal by typing a few letters. Collapse the menu with its arrow; SnagBane remembers it.
Light or dark. Each person picks Light, Dark or Follow the device in the account menu; administrators set the default for everyone in Settings → General & brand → Light or dark. Forms, portals and approval pages that people fill in stay light.
Start from a template
New form (on Forms or in the top bar) shows the template library under Or start from a template: about 120 ready forms in 14 groups — IT, HR, Finance, Purchasing, Operations, Facilities, Health & safety, Legal & compliance, Sales & marketing, Customer service, Feedback & surveys, Events, Training & education and Projects. Search by name (“leave”, “laptop”, “expense”) or pick a group, then click a template: the form is created with its questions, and many come with approval steps already set (for example manager first, then Finance). Change anything in the builder. You can also start from a blank form, paste your questions or bring a form from Microsoft Forms, Google Forms or InfoPath.
Paste your questions (fastest start)
Click New form → Paste your questions and paste a list — from Word, an email or an old paper form. One question per line; SnagBane picks the type from the wording (email, date, number, rating, person, file, long answer…) and you can change any type in the preview before creating the form.
- Lines starting with
-under a question become its choices (2 choices “Yes/No” become a yes/no question, more than 6 a dropdown); use[ ]for “pick several”. - End a question with
*to make it required. A line---starts a new page; a first line starting with#becomes the form title. - Force a type with a hint at the end:
[date],[long],[email],[number],[dropdown],[multiple],[person],[file],[stars]. - A range in brackets:
(1-5)(1 to at most 5) becomes a star rating; other ranges such as(0-10)or(1-7)become a scale (numbers to click). Change it in the preview if you prefer the other one. - The respondent's own details are filled in for signed-in people: a question like Name / Ime i prezime, Email, Job title or Department gets “prefill from profile”, and a person question about the manager gets the respondent's manager. Questions about someone else (“Manager's email”) are left alone.
- When the questions are in Serbian or Croatian, the form's language is set to match, so buttons and messages (Submit, This question is required.) are in that language too — change it in the form's Languages tab.
Bring your form (Microsoft Forms, Google Forms, InfoPath)
Click New form → Bring your form. SnagBane reads the questions and shows them first; check the types, then Create form. Approval steps, conditions and integrations are added in SnagBane afterwards.
- Google Forms — paste the link people open to fill in the form (
docs.google.com/forms/…orforms.gle/…). The form must accept responses from anyone: if it is limited to your organisation, switch that off for a minute, import, then switch it back. Sections, choices, “Other”, grids, scales, ratings, dates and file questions come along. - Microsoft Forms — paste the form's share link (
forms.office.com/…,forms.cloud.microsoft/…). Microsoft has no public API for a form's questions, so SnagBane reads what the response page itself loads; this works for forms that anyone can fill in. If the form needs sign-in (or Microsoft changes that page), open the form, copy its questions and use Paste your questions. - InfoPath — upload the form template (
.xsn), or a.zipof the files from InfoPath's File → Publish → Export Source Files. The main view's controls become questions (text boxes, date pickers, drop-downs, option buttons, check boxes, attachments, people pickers, repeating tables), with their labels and required settings. Rules, calculations and lists filled from data connections are not imported — the preview lists what to add again. - The server needs outbound HTTPS to Google or Microsoft for link imports; InfoPath files are read entirely on your server (nothing is sent anywhere).
Form builder
Click a question type on the left (or drag it onto the canvas), then edit it in the right-hand panel. Drag the handle to reorder, or hover a question and click the round + below it to insert a new one right there. Everything saves automatically. Turn on Live preview (wide screens) to try the form on desktop or mobile while you build — logic, branching and validation work, nothing is saved. Less-used options (show-if conditions, placeholders, length limits, prefill, variable names) are under Show advanced options in the question panel. If two people (or two browser tabs) edit the same form, nothing is overwritten silently: the later save stops and asks whether to load the newest version or keep yours. The available types are short and long answer, email, number, phone, website, single choice, multiple choice, dropdown, yes/no, star rating, scale/NPS, date, time, file upload, hidden field, person picker, heading, text block and page break.
Person picker & Microsoft 365 prefill
A Person picker question searches your tenant as the respondent types. It matches display name, email and UPN, and shows job title and department. Turn on Allow multiple people to collect several.
Several directories (Business): when more than one of Microsoft 365, Google Workspace and Okta is connected, each Person picker question has Search people in — one directory or SnagBane accounts (people with an account here). Results are never mixed. Without a choice the question uses the default directory (Settings → Security → Default directory). The choice only appears when there is something to choose.
Prefill from Microsoft 365 fills a question from the signed-in respondent's Entra ID profile. You can use the respondent, their manager, display name, email, job title, department, office, mobile or employee ID. Respondents can still change the value.
Fast person search. SnagBane keeps a small local copy of the directory (enabled users only: name, email, job title, department, office, manager), so the picker answers in milliseconds even in tenants with tens of thousands of people and never hits Microsoft's rate limits. It refreshes every night (02:30) and catches up hourly if a run was missed; Settings → Microsoft 365 → Fast person search shows the status and has Sync now. From the command line: php artisan snagbane:directory-sync. Until the first sync finishes, searches go live to Microsoft Graph / Google as before. Needs the scheduler (cron * * * * * php artisan schedule:run) — already running in the Docker stack.
Deleted people and groups. Remove user (Users & roles) first hands over or deletes the person's forms, then keeps the account for 30 days under Deleted people: it can't sign in, its sessions and remember-me cookies end, and its email address stays taken (no admin, sign-in or provisioning can create a new account with it). Restore gives it back with its workspace memberships (they sign in again); Delete permanently removes it now. After 30 days SnagBane removes it for good every night, and the address becomes free. Deleted groups work the same under Settings → Groups → Deleted groups. Only want to stop someone's access? Use Deactivate account — nothing is deleted. Audit entries: user.deleted, user.restored, user.purged (and group.*).
People who leave (offboarding, Enterprise plan). Accounts in Users & roles exist only for people who signed in or were added by an admin — the directory copy doesn't create any. Works the same for Microsoft 365, Google Workspace (after each directory sync: suspended or archived = disabled, not found = deleted, with Google's deletion date while the user is in Google's deleted users, 20 days) and Okta (checked with the Okta API once a day — see Okta). After each sync from Microsoft 365, SnagBane checks every account linked to an Entra account that is no longer in the copy — only accounts whose email domain is one of your tenant's (demo accounts such as @contoso.demo are never taken for people who left) — one by one with Microsoft: deleted (not found; the card shows Microsoft's deletion date while the account is in Entra's deleted users, 30 days) or disabled (sign-in blocked; the card shows when SnagBane noticed it) accounts are switched off — signed out, no sign-in, no API tokens — and listed under Users & roles → People who left the organisation with what they own (forms, portals, integrations, webhooks) and the approvals waiting for them. Nothing is deleted: their forms keep collecting responses. Hand over forms and portals moves everything to someone else; Done takes the person off the list. Safeguards: the only active owner is listed but stays on; when Microsoft confirms more than usual at once (more than 10, and more than a fifth of the linked accounts — a reorganisation, a test tenant), nobody is switched off yet: the card lists them with Switch these accounts off or Ignore (ignored people aren't proposed again; you can still deactivate them by hand); reactivating someone removes the mark. Switch it off under Fast person search → Switch off accounts of people who leave. Every change is in the audit log (user.left_organisation).
Only some groups in Google Workspace (Google Workspace → Who can use SnagBane, Enterprise plan). The same choice for Google: pick up to 20 Google groups (members of groups inside them count too). Others are refused at “Sign in with Google” (checked with Google every time) and aren't in the directory copy (their managers are kept). Needs the Directory API feature (the read-only group scope is already part of it). Owners and admins can always sign in.
Only some groups (Microsoft 365 → Who can use SnagBane, Enterprise plan). By default everyone in your tenant can sign in with Microsoft. Choose Only members of these groups and pick up to 20 Entra groups (people in groups inside them count too) to limit it: others get “SnagBane is only for some groups” at sign-in (web and Teams; checked with Microsoft every time), aren't in the directory copy and person search (their managers are kept, so Manager approvals still work), and “install for everyone” in Teams installs the apps only for the groups' members. Owners and admins can always sign in. Needs the permission Group-based access & role mapping (GroupMember.Read.All) — tick it under Re-run setup. Entra's own Assignment required on the enterprise application works as well, but assigning groups there needs Entra ID P1 or P2 and doesn't include nested groups. Changes are in the audit log (m365.scope_updated).
Conditional logic
Enable Conditional logic on any question to show it only when earlier answers match. For example, show "Business justification" when "Estimated cost" is greater than 500. Hidden questions are never validated or stored, and the rule is enforced again on the server.
Branching (side-by-side columns)
Give each answer its own questions. Select a single-choice, dropdown or yes/no question and click Add branching. Lines go out from the question into columns side by side, one per answer, like a switch in Power Automate, and join again below: the questions under the columns are the shared part everyone answers. You see the whole logic at once, without scrolling up and down.
- Questions in a column — click + Add question in a column, drag a question (or a type from the left) into it, or right-click a question and pick Move to column….
- Names — a column is named after its answers (“Unpaid, Other”) and follows them when you change the answers. Double-click the name (or right-click → Rename) to give it your own name.
- Several answers, one column — drag a column's header onto another column to put their answers together; right-click → Split into one column per answer separates them again.
- End of a column — the button at the bottom of each column switches between Continue below (the shared questions) and End the form. A column without questions sends that answer straight on.
- Same page or its own section — a column's questions appear right under the answer, on the same page. For longer branches, right-click the column → Open as its own section: respondents then get a page for it, titled with the column's name. There is no section to manage for the join; your own sections (New section / page) stay separate from branching.
- Right-click a question or a column for its actions: add a question, rename, move left/right, join or split columns, move a question into a column or below the columns, remove branching.
- Remove branching keeps the questions of every column, one after another.
Example: “Leave type” — Annual leave (no extra questions), Sick leave → “From” and “To”, Unpaid, Other → End the form; then everyone else answers “Who covers for you?” and “Manager”.
An unanswered branching question (optional, or hidden by a condition) skips all columns. Answers from columns the respondent didn't take are never saved. Forms made before columns existed keep their “go to” menus and keep working; when the branching means the same thing as columns (at most one later target), the question card offers Show as columns. Columns made with 2.10 stay sections of their own. Branching inside a column is not available yet.
For more complex forms:
- Sections — add a New section / page break to split a form into pages with their own title; answers can also jump to a section.
- After this section — on a section break, set where respondents go next by default, plus routing rules with several conditions (all / any, numbers, text, empty/answered).
- Show only if… (under Show advanced options) — show a question only when earlier answers match, e.g. several conditions combined.
- The Logic overview tab lists every branch, rule and conditional question in plain language.
Answers to skipped questions and sections the respondent never visited are discarded, and the server recalculates the path, so hidden questions can't be forced in.
Calculations & answer piping
Add a Calculation question and write a formula such as {qty} * {price} or SUM({items.amount}). Supported: + - * / %, comparisons, AND/OR, SUM AVG MIN MAX ROUND FLOOR CEIL ABS COUNT IF CONCAT DAYS TODAY. Give choice options a numeric value to score them. Totals update live for respondents and are always recomputed on the server.
Piping: put {{variable_name}} in any question, description, confirmation or receipt message to insert an earlier answer.
Repeating tables, grids, ranking, places, dependent lists, options from a list, “Other”, consent, dates & signatures
- Repeating table — line items (expenses, attendees, equipment) with text, number, date and choice columns, min/max rows and live column totals.
- Grid (matrix) — rate several rows on the same scale, one or several answers per row. Likert scale in the question list adds a grid with the five agree–disagree columns (or click Use agree–disagree scale under a grid's columns).
- Ranking — respondents tap the options in order, most important first, and can reorder them by dragging or with the arrows. How many to put in order asks for the top N only. The summary lists the options by average place and how many people put each one first; exports show “1. Price; 2. Quality”.
- Places per option — on single choice, multiple choice and dropdown questions, turn on Limit places per option and type how many people can choose each option (seats in a session, time slots, T-shirt sizes). A full option is shown as Full and can't be chosen; the server checks again when the form is sent, so two people can't take the last place. Rejected requests give their place back; editing a response keeps the place it holds. Show places left shows “3 places left” next to each option, and the summary shows “12 of 20 places taken”.
- Dependent lists — on single choice, multiple choice and dropdown questions, Show options depending on picks an earlier choice, dropdown or yes/no question; next to each option you choose the answer it belongs to (for example cities under their country). Respondents see only the options of the answer they gave (options left on Any answer always show), a changed answer unticks what no longer fits, and the server accepts only matching options. When nothing belongs to the answer, the question isn't required.
- Import options — Import next to Bulk edit takes a CSV file (Excel, Google Sheets or SharePoint: export to CSV) or pasted cells; choose the column with the names and, for a dependent list, the column with the answer each belongs to (matched by name) and the places per option. Replace options keeps the ids of options whose name stays the same.
- Options from a list — Take the options from a list copies them from a CSV file on the web (a link that downloads the file, e.g. a published sheet) or, in Enterprise, a SharePoint list, an Excel table (through the Excel account in Settings → Microsoft 365) or a Google Sheets tab. Read the columns, pick the column with the names (and the answer it belongs to, and places), then Load the options. SnagBane refreshes them every hour; a name that disappears from the list can no longer be chosen, while earlier answers keep it. A failed refresh keeps the last options and shows the error on the question. Like integrations, people who aren't administrators can only use SharePoint and OneDrive addresses an administrator allowed (Settings → Security).
- “Other” with a text box — on single choice, multiple choice and dropdown questions, turn on Add “Other” with a text box. People who pick it type their own answer; it is required once “Other” is chosen. Responses, exports and integrations show the typed text, the summary counts all of them as “Other”, and conditions can test “is Other”.
- Shuffle the options — everyone sees the options of that question in a different order (useful against order bias in surveys); “Other” stays last. Answers are stored the same way, so results aren't affected.
- Date and time and Date range — one question for a date with a time (meeting, pickup), or a from–to pair (leave, travel). The end can't be before the start, and min/max dates apply to both. In calculations a date range counts its days, both ends included, so
{leave} * 8gives hours of leave. - Amount — a number with a currency (EUR, USD, RSD… any ISO currency). People type the number next to the currency sign; it is stored as a plain number, so totals, formulas and conditions work, and results, exports and PDFs show it with the currency (“1,250.50 EUR”). Decimals follow the currency (none for JPY, three for KWD); min/max apply.
- Slider — pick a value between a minimum and a maximum in steps, with an optional unit (%, km, hours). Stored as a number like a rating, so averages and conditions work; leave it untouched and the question stays unanswered.
- Address — street and number, an optional second line, postal code, city, optional region and a country from the list (names in the visitor's language, with a preselected country). When the question is required, street, city and country are required. Results and exports show one line (“Knez Mihailova 1, 11000 Beograd, Serbia”); conditions and piping use that text.
- Consent — a checkbox with the text people agree to and an optional link to the full terms. The response stores the text that was shown and the server's time of the consent (“Accepted 2026-10-07 09:15 UTC”); it is required by default.
- Signature — draw with mouse, finger or pen; stored as a private PNG and embedded in printouts.
- Validation — min/max length, formats (letters, digits, postcode, IBAN or your own pattern), whole numbers, date ranges and min/max selections.
Quiz mode
In Settings → Quiz mode turn on grading, then mark correct answers (✓ next to options, or accepted text answers) and points per question. Respondents can see their score immediately; results show the average score, a distribution and the % correct per question. Answer keys are never sent to the browser.
Approval workflows
Open a form's Approvals tab and add stages. Each stage's approver can be the person chosen in a question, the respondent's manager from Microsoft 365 / Google Workspace, specific people, email addresses, or the members of a group — a SnagBane group (made by hand or linked to Entra ID, Google, Okta or SAML) or a group straight from a connected directory; its members are read when the stage starts (active people with an email, up to 25), so people joining or leaving the group count. With several directories connected, Specific people can search a chosen one, and a person picked in a question is checked against that question's directory. Choose whether any one or everyone must approve, add reminders and optional conditions (e.g. only send to Finance when the total is over 500).
Approvers get a branded email with Review & approve and Reject buttons (single-use, secure links) and also see requests under Approvals in SnagBane. Uploaded files and signatures of the request open straight from the approval page or the inbox, even for approvers who have no access to the form itself. The requester is emailed the outcome, and every step is recorded. If a form lets respondents edit after submitting, a rejected request can be corrected with the edit link and then goes through approval again from the first stage. Events approval.requested, approval.approved and approval.rejected can trigger integrations and webhooks.
How approvals work, step by step
- Someone submits the form → the response gets status pending and the first stage starts (stages whose condition doesn't match are skipped).
- The stage's approver(s) get an email with single-use Approve/Reject links and see it in Approvals. With “any” one decision completes the stage; with “all” everyone must approve. Rejecting requires a comment.
- Approved → next stage; after the last stage the response is approved. Any rejection ends it as rejected. The requester is emailed the outcome and sees it live under My requests on a portal.
- Change approver: when an approver is away or doesn't answer, an administrator (or the owner of the form's workspace) hands the waiting approval to someone else — Home → Waiting longest / Overdue approvals, or the request under Results → Approvals. The new approver is notified right away, the old link stops working, and the audit log keeps who changed it, from whom to whom and the optional note. The requester and deactivated accounts can't be picked.
- Withdraw or stop a request: changed your mind or sent it by mistake? Under Approvals → My requests the requester clicks Withdraw while it is still waiting; people who can edit the form (and administrators, workspace owners) use Stop approval on the response in Results. A reason is optional. The request becomes Withdrawn: waiting approvers are told by email, reminders stop, emailed approval links only show that it was withdrawn, and no integration runs. When someone else stopped it, the requester gets an email too. With Allow editing after submitting, the requester can correct it with their edit link and send it again — the approval starts over.
- Overdue approvers get reminders (due days per stage). If an approver can't be resolved (no manager in the directory, empty person question) the task goes to the form owner, so nothing is auto-approved by mistake.
- The requester can't approve their own request (a switch per stage, on for new stages): if the requester picks themselves, or is one of the approvers, the task goes to the other approvers; if nobody is left, to the requester's manager, else the form owner, or another administrator when the owner is the requester.
- Each step fires events for Power Automate, Teams, Slack and webhooks — e.g. create the laptop order only after
approval.approved.
What happens next: actions after approval or rejection
Like a Power Automate approval flow, but inside SnagBane: every integration (Excel, Google Sheets, SharePoint, Airtable, Jira, Send email…) and every automation (Make, n8n, Zapier, Power Automate, webhooks) chooses When does this run?
- Right away — when someone sends the form, whatever the approvers decide later (e.g. a Teams message “new request”).
- After approval — only when the request is approved, after the last approval step (e.g. add the Excel row, create the order, put it in the calendar).
- After rejection — only when an approver rejects it (e.g. email the team lead, log it in a sheet).
- More events — several at once, or edits, “approval requested”, form published / closed.
On a form with approvals, new integrations that write data start as After approval, and a warning shows when one is set to run right away. The Approvals tab lists under What happens next what runs right away, after approval and after rejection, including the email to the requester. Merge tags {{event.by}} (who approved or rejected) and {{event.comment}} (their comment) fill in the last decision; webhook payloads carry it as "approval": {"decision": "approved", "by": "…", "comment": "…"}. Send test and Test send the event the integration or automation waits for, so Make and n8n learn the right structure. Only when… conditions still apply, so e.g. “after approval, only when the total is over 500” works.
Signatures after approval (DocuSign, Adobe Acrobat Sign, Dropbox Sign — Business)
Add the DocuSign integration in a form's Connect apps tab (administrators). When the last approval stage approves, SnagBane sends the PDF of the response as a DocuSign envelope to the signers you list (merge tags like {{respondent.email}} work), one after another or all at once; the PDF ends with a signature and date line per signer where DocuSign places its fields. Your organisation uses its own DocuSign account (JWT: integration key, RSA key and the user who sends; see Setup guides).
SnagBane checks each envelope itself — every 15 minutes at first, hourly after two days, every six hours after two weeks, until 120 days — so your server needs no address that DocuSign can reach. Check now on the response asks at once. When everyone has signed, the signed PDF (with DocuSign's certificate of completion, unless you turn it off) is saved with the response under Signatures, and the event signature.completed runs: a SharePoint, Google Drive, Dropbox or S3 integration set to After signing uploads it together with the other files. Declined or voided envelopes show the reason and run signature.declined.
Adobe Acrobat Sign and Dropbox Sign work the same way: the response's PDF goes out after the last approval, SnagBane follows the agreement or signature request itself and stores the signed PDF on the response, and After signing / Signature declined run your other integrations. Acrobat Sign uses your own API application (application ID and secret) and one sign-in with Connect Acrobat Sign; it needs SnagBane on an https:// address, because Acrobat Sign sends you back to it after the sign-in. The connection stays alive while it is used — SnagBane renews it weekly — and adds Acrobat Sign's audit report to the signed PDF unless you turn it off. Dropbox Sign needs only the account's API key; Test mode lets you try it with requests that are not legally binding (without a paid Dropbox Sign API plan they can only go to the account owner’s own address), and Dropbox Sign records the signing time in its own audit trail, so its PDFs have no date line. The setup steps for each are under integration setup guides.
Phone app (install SnagBane) — Business
SnagBane can be installed as an app on phones, tablets and computers — straight from your own SnagBane site, no app store and nothing sent to us. The app opens without the browser bars, shows your organisation's name and colour, and can notify approvers when a request is waiting for them.
- Open SnagBane in the browser on the phone and sign in. Under Approvals you see Approvals on your phone.
- Install: tap Install app where the browser offers it (Chrome, Edge on Android and desktop). On iPhone and iPad, add SnagBane to the Home Screen from Safari and open it from there.
- Notifications: tap Turn on notifications and allow them. Each device is turned on separately; turn it off with the same button.
- Requirements: the site must use
https://(browsers only allow installable apps and notifications on secure sites), and the SnagBane server must be able to reach the browser's push service on the internet over HTTPS (Google for Chrome and Android, Apple for Safari, Mozilla for Firefox, Microsoft for Edge). On iPhone and iPad, notifications work in the app added to the Home Screen (iOS / iPadOS 16.4 or later). - Privacy: a push carries no content. The phone is only told “something is new”; the app then asks your SnagBane server what is waiting and shows it. Form titles, names and answers never pass through Google, Apple, Mozilla or Microsoft.
- The notification opens Approvals, where the request can be approved with one tap. The app icon shows how many requests are waiting, where the phone supports it.
Workspaces, roles & groups
Workspaces are spaces for teams or departments. Every form, portal and workspace-wide integration belongs to one workspace; members see only their workspaces. Use the switcher at the top of the sidebar to work in one workspace or see all. Create one under Workspaces → New workspace (name, colour, icon, then members).
- Workspace roles: Owner — members, settings, workspace integrations and the approvals overview (with Reassign when an approver is away); Editor — creates forms and portals; Viewer — sees forms and results.
- Organisation roles (Users & roles): Owner — everything; Administrator — all workspaces and organisation settings, but not security, Microsoft 365 / Google connections, licence or system; User & access admin — editor plus users, groups and workspace members; Editor; Viewer. The full matrix is under “What each role can do”.
- Groups (Settings → Groups): manual, or linked to an Entra ID, Google Workspace, Okta or SAML group (membership checked live; Okta live through the optional Okta API connection, otherwise from the groups sent at sign-in). Open a group to see its members, a page at a time — straight from the directory for linked groups, with who has no SnagBane account yet. Add groups to workspaces, and optionally give every member an organisation role (e.g. group “IT admins” → Administrator).
- Organization settings decide who may create workspaces and whether new users join a workspace automatically (off by default; when on, you pick the workspace and their role there — Viewer by default).
Portals
A portal is one page that lists several forms — like an HR or IT service desk. Open Portals → New portal (or New → Portal in the top bar), pick a look from the pictures, tick the forms to include and click Create portal. There are 20 looks, and you can switch any time under Design → Look without losing content:
- Modern — a company service desk: a greeting for signed-in colleagues, their latest requests with the approval steps (“Step 1 of 2 · Manager”) and the services as a tidy list with the time to fill in and who approves.
- Classic — a colourful banner with the forms as cards, a list or tiles.
- Showcase — like a team's home page in Microsoft Teams: a light header with What we cover, large form cards and a Didn't find it? band. A new Showcase portal starts with these parts filled in.
- Hub (side menu, forms as tiles), Launcher (one big search, forms as app icons), Magazine (headline, featured form, numbered forms), Directory (one A–Z table — good for many forms), Tabs (one tab per section), Board (sections as columns), Minimal (text only), Bento (tiles in different sizes and colours), Split (title and search on a fixed panel), Guided (pick a topic first), Night (dark page, glass cards), Checklist (numbered steps, for onboarding), Intranet (cover banner, notices in a side column), Gallery (rows of tall colour cards), Help centre (big search, one card per section), App (a phone home screen) and Bold (a poster with one strong colour).
Every look has search, sign-in and My requests with the approval steps. Choosing a look also applies its colours while the portal still uses some look's colours; once you set your own, Use this look's colours brings them back.
In the editor:
- Content: group forms into categories, reorder them, add links (intranet, handbook) and blocks (text, image, buttons, info cards, FAQ, notice, contacts, Call to action, divider), and give each item its own title, description, icon, colour or a bigger “highlight” card. Leave the title empty to use the form's own (translated) title.
- Design: the look (one of the 20 above); for Classic: cards, list or tiles; cards per row; banner, simple or no header; accent, background and header colours; upload a logo and cover (or pick a ready-made cover); card style and corners. The preview on the right updates live (desktop/mobile).
- Access & options: anyone with the link, your organisation, or specific people/groups. New forms and portals start as My organisation (since 2.61.5); make one public on purpose under Share. Enterprise plan: Settings → Security → Allow anonymous access off means no form or portal opens without signing in, also ones set to Anyone with the link (they keep that setting and open again when it is back on); emailed approval links and receipts keep working. Each form keeps its own access rules: people only see forms they can fill in (or, if you prefer, greyed out with a sign-in hint). Toggle search, descriptions and the My requests tab, add footer text and choose the web address (
/p/hr). - Languages: offer the portal in other languages. Pick the language you write the portal in, add a language and translate the title, welcome text, footer, categories, links and every block (or Translate missing automatically with DeepL, Azure AI Translator, Google or LibreTranslate; CSV export/import for translators). Visitors see the portal in their language (language picker, their profile or their browser); texts that are not translated yet appear in the main language. Forms on the portal use their own translations. Preview shows a translation in the live preview; texts that changed after they were translated are marked for review.
Company portal (Business, administrators): at the top of Portals, click Set up. You get one page for the whole company, in the Modern look: every workspace is a department (IT, HR, Finance…) and its published forms appear by themselves — no picking forms by hand. Everyone sees only the forms they may open, and My requests covers every department. Under Content → Departments hide a workspace or change the order; notices, FAQ and contacts you add appear under the departments. Visitors can pin their own department (the pin next to it) so it opens first for them. Publish it and add it to Microsoft Teams as the company tab (Microsoft Teams in the editor). Without a Business licence the card shows what it adds.
Press Publish and share the link or put it on your intranet/Teams tab. Editors see and manage their own portals; admins see all. Draft portals and draft forms are only visible to their editors.
Page builder: in the portal editor's Content tab, add blocks with + or Add block — form categories, text (supports **bold**, *italic*, [links](https://…) and “- ” lists), images, buttons, info cards, FAQ, notices, contacts and dividers. Reorder, duplicate, and give each block a background.
Own domains for portals
Settings → Domains → add e.g. hr.company.com and choose the portal. Then connect it:
- Shared hosting (cPanel, Plesk…): create the subdomain and set its document root to the same
publicfolder as SnagBane; enable AutoSSL / Let's Encrypt. - VPS / Docker: point DNS at the server (CNAME or A record) and add the host to your web server or reverse proxy with a certificate.
- Click Check. SnagBane confirms the domain reaches this installation (or a TXT record
_snagbane.<domain>with the shown token). Sign-in with Microsoft/Google happens on the main address (APP_URL); people then return to the portal on its own domain, already signed in (a single-use hand-over code, valid for one minute and only for that verified domain).
Links SnagBane sends out (approval emails, notifications, webhooks) always use APP_URL, or the custom domain the person is on — never an address taken from an unverified request, so they can't be spoofed.
Plans & licence
Free needs no licence and is complete on its own for up to 25 people. Business connects SnagBane to Microsoft 365, Google Workspace, Okta and SAML and unlocks every integration, the REST API, payments and the phone app. Enterprise is Business plus the security package. Inside the app every paid feature carries its plan: an amber Business badge (Business and Enterprise) or a violet Enterprise badge (Enterprise only), with a lock while your licence doesn't include it. Settings → Licence & plan lists what each plan adds.
| Free | Business | Enterprise | |
|---|---|---|---|
| People (active accounts, any role) | up to 25 | up to 50, 100, 250, 500 or unlimited | up to 100, 250, 500 or unlimited |
| Forms, builder, logic, calculations, quizzes, files, signatures, translations, PDF | ✓ | ✓ | ✓ |
| Workspaces, roles, manual groups, approvals (all stages), portals, own domains | ✓ | ✓ | ✓ |
| Sign-in | SnagBane accounts, two-step verification | + Microsoft, Google, Okta and SAML 2.0 | + sign-in limited to chosen directory groups |
| Person picker & prefill | from SnagBane users | from Entra ID / Google Workspace / Okta, fast local directory copy | ✓ |
| Groups from Entra ID / Google / Okta / SAML | — | ✓ | ✓ |
| SMTP, SendGrid, Mailgun, Postmark, Brevo, Resend, Amazon SES | + Microsoft 365 mailbox, Gmail | ✓ | |
| Integrations | Make, n8n, signed webhooks, HTTP request, Discord, Telegram, Twilio SMS & WhatsApp, Dropbox, Notion, Airtable, newsletter tools, email, local export | + Teams, Slack, S3, SharePoint, Excel, Google Sheets, Drive, Chat, Jira, ClickUp, Asana, monday.com, Trello, Outlook and Google Calendar, Dynamics 365, Salesforce, DocuSign, Acrobat Sign, Dropbox Sign | ✓ |
| Power Automate & Zapier, Power Automate custom connector, REST API & tokens | — | ✓ | ✓ |
| Payments in forms (Stripe, PayPal), phone app, portals as Teams apps, company portal, white-label | — | ✓ | ✓ |
| Security package: audit log streamed to your SIEM, SCIM provisioning, IP allowlist and block list, data retention policy, automatic offboarding | — | — | ✓ |
| Installations per licence | any number | 1 | 2 (production + staging) |
Free download → full package: the free download does not contain the code of the paid features. Activate your licence (below), then click Install the full package on the Licence page (or Settings → System & updates → Updates → Get the full package) and Update now: it installs over your copy — nothing to download, your data and settings stay.
Activate: Settings → Licence & plan, paste the key from your purchase email or your Freemius account, click Activate. The server needs outgoing HTTPS to api.freemius.com and to the SnagBane licence and update service snagbane-updates.markostepanovic.workers.dev; SnagBane re-checks the licence once a day. Deactivate on this server frees the licence so you can use it on another server.
Move a licence to another server: on the old server click Settings → Licence & plan → Deactivate on this server, then activate the same key on the new one. If the old server is gone, sign in to your Freemius account, open its Activations (or Websites) and click Deactivate next to the old server. A key whose activations are all in use can't be activated again; a server whose licence was deactivated from the account pauses paid features at its next daily check (at the latest after 2 days).
- No grace period: when the licence ends (expired or cancelled), paid features pause right away. If the licence service can't be reached, they keep working for at most 2 days while SnagBane retries every hour — Settings → Licence & plan shows when they would pause.
- While paused, forms, responses, approvals, workspaces and groups keep working. Directory sign-in, own domains, fast person search, paid integrations and the security package pause, and their settings can't be changed until you renew. Nothing is deleted: your data and settings stay on your server, and once the licence is active again (renew, or activate a new key) everything continues where it was.
- Only a licence unlocks a paid plan: the licence service signs a proof of your Freemius licence (plan and number of people included) that SnagBane checks itself; settings, the database or the server address can't turn a plan on.
Approvals in Microsoft Teams (Business)
Approvers get a notification in their Teams activity feed when a request waits for them (and when it's overdue or someone clicks Remind). The notification opens the request inside Teams, signed in with their Teams account, where they approve or reject it. Email and phone notifications keep working; no Power Automate flow and no bot are needed.
- Permission: Settings → Microsoft 365 → Re-run setup / change permissions, tick Notify approvers in Microsoft Teams (application permissions
TeamsActivity.SendandAppCatalog.Read.All— the second one finds the app's id in your org catalog) and sign in once as a Global Administrator. - Teams sign-in must be on (card Microsoft Teams sign-in on the same page): the app signs people in with it.
- On the card Approvals in Microsoft Teams, click Download the Teams app and upload the zip in Teams Admin Center → Teams apps → Manage apps → Upload new app. To install it for everyone: Teams apps → Setup policies → Global → Installed apps → Add apps. Microsoft only delivers the notification to people who have the app installed.
- Turn the card's switch on, pick a person from your organisation who has the app (or leave it empty to send it to yourself) and click Send a test notification. The person needs a Teams licence in the tenant; an account from outside the tenant can't get Teams notifications.
Publish and install from SnagBane (instead of the zip): on the card, Publish to Teams (a Teams administrator signs in once) and Install for everyone or a group, as described for portals. With the permission Install Teams apps for people automatically, SnagBane also installs the app for an approver who doesn't have it yet when a request waits for them, then sends the notification (switch on the card; on by default). The app's icon and colour come from Settings → General & brand.
If Install app on the app's page in Teams Admin Center fails, use the setup policy above, or let people add it themselves (Teams → Apps → Built for your org). A notification that fails with “doesn't have the Approvals app” means the app isn't installed for that person yet. The tab always works as the person signed in to Teams, even if someone else is signed in to SnagBane in the same browser.
The app has one personal tab, Approvals, that lists what waits for you. If a notification can't be delivered (for example the app isn't installed for that person), the card shows the last error; the request itself is never affected. After changing the site address or re-running Teams sign-in, download the app again and upload it as an update.
Portals in Microsoft Teams
Open a portal in the editor and click Microsoft Teams — the same two-option guide as below is built in, with your links pre-filled. Both options need SnagBane on a public HTTPS address (Teams never loads http://). For a quick test on your PC, a tunnel such as Cloudflare Tunnel or ngrok gives you an https address; set it as the site URL.
Option 1 — Teams app (recommended)
The portal becomes an app with its own icon in the Teams app bar, can be added to any channel or chat, and people are signed in automatically with their Teams account (“My requests”, organisation-only forms and manager prefill all work).
Fastest (Business, administrators): in the same dialog, Publish to Teams uploads the app to your organisation's Teams apps after a Teams administrator signs in once with Microsoft (SnagBane uses that sign-in only for this upload and doesn't keep it). Install then adds it for everyone or for one Microsoft 365 group, so it appears in their Teams without them doing anything. This needs the Microsoft 365 permission Install Teams apps for people automatically (AppCatalog.ReadWrite.All delegated — the Teams administrator who publishes signs in with it; AppCatalog.Submit could only send the app for review — TeamsAppInstallation.ReadWriteSelfForUser.All and AppCatalog.Read.All application; the install permission only covers Teams apps that belong to SnagBane's own app registration) — tick it under Re-run setup / change permissions. Installing for everyone needs Person picker & profile prefill, for a group Group-based access & role mapping. People who join later: run Install again, or add the app to a setup policy. Without these permissions, use the zip:
- Download the app package: portal editor → Microsoft Teams → Teams app → Download Teams app (.zip). The zip holds the manifest (name, description, address) and icons made from the portal's logo (or the organisation's logo) in the portal colour — nothing to edit.
- Upload it: a Teams administrator opens admin.teams.microsoft.com → Teams apps → Manage apps → Upload new app and selects the zip.
- Make it available: check that the app is Allowed. To pin it for everyone: Teams apps → Setup policies → Global → Pinned apps → Add apps.
- Use it: people open it from Apps (or the pinned icon). To add it to a channel: open the channel → + → search for the app → Save.
- Just testing? Without admin rights: Teams → Apps → Manage your apps → Upload an app → Upload a custom app (if your organisation allows custom apps).
Automatic sign-in (Teams single sign-on) — Settings → Microsoft 365 → Microsoft Teams sign-in → Enable Teams sign-in. A Global Administrator signs in once more with Sign in with Microsoft (the same copy-the-address steps as the setup); SnagBane then adds “Expose an API” (api://<your-host>/<client-id>, scope access_as_user, pre-authorised for Teams, Microsoft 365 and Outlook) to its existing app registration. New installations on HTTPS get this automatically during the Microsoft 365 setup. Download the package again afterwards (it then contains the single sign-on block) and choose Update in Teams Admin Center. Without single sign-on the app still works: people click Sign in once and sign in in a pop-up (Microsoft, Google or email).
Manual / PowerShell setups: in Entra ID → App registrations → SnagBane Forms → Expose an API, set the Application ID URI to api://<your-host>/<client-id>, add a scope access_as_user (admins and users), and add the client applications 1fec8e78-bce4-4aaf-ab1b-5451cc387264 (Teams desktop/mobile) and 5e3ce6c0-2b1f-4285-8d4b-75ee78787346 (Teams web) for that scope. Then run Enable Teams sign-in once or set it on in Settings → Microsoft 365.
Updates: changes to forms, categories and design appear in Teams immediately. Only a new portal name or colour needs a new package (Update in Teams Admin Center).
Option 2 — Website tab (no admin needed)
- Portal editor → Microsoft Teams → Website tab → Copy the tab link (it ends in
?teams=1). - In Teams, open the channel → + (Add a tab) → Website → give the tab a name, paste the link → Save.
- The portal is a tab for everyone in the channel; forms open inside it with a Back to the portal link.
Limits: Teams doesn't allow signing in inside a website tab, so use it for portals set to Anyone with the link. “My requests” and organisation-only forms need the Teams app (Option 1).
Technical notes
- Portals (
/p/…) may be framed by Teams / Microsoft 365 / Outlook hosts only. Add more hosts (e.g. your SharePoint) withSNAGBANE_PORTAL_FRAME_ANCESTORS="https://contoso.sharepoint.com". Public forms (/f/…) stay embeddable anywhere (SNAGBANE_FRAME_ANCESTORS). - On HTTPS, session cookies are sent as
SameSite=None; Secure; Partitionedso sign-in works inside Teams (CSRF tokens still protect every request). SetSNAGBANE_EMBED_COOKIES=falseto keepSameSite=Laxif you don't use Teams. - Teams single sign-on tokens are verified with Microsoft's signing keys (signature, tenant, audience, expiry,
access_as_userscope) before a session is created; the pop-up fallback hands over a single-use code valid for two minutes.
Send a form to people, with reminders
Share → Send to people emails a published form to SnagBane accounts, whole groups (their active members) or pasted addresses — up to 1,000 at a time. Each person gets a personal link, so the list shows who has answered and who hasn't (people who answer while signed in are recognised by their account too).
- Automatic reminders: choose how often (every 1–14 days) and how many times (up to 5); SnagBane checks every hour and stops when the person answers, when the form closes or when you remove them from the list.
- Remind now for one person or everyone who hasn't answered (at most once an hour per person). Addresses that couldn't be reached are marked Not delivered with the reason; Send again retries.
- Sending again skips people who already got the form. An optional message is added to the email.
- Anonymous forms: SnagBane only notes that a person answered, never which response is theirs.
- The link doesn't give access by itself: a form for signed-in colleagues still asks them to sign in. Email must be set up first (Settings → Email).
Save & resume, editing and receipts
- Save & finish later gives respondents a private link valid for 30 days.
- Let respondents edit after submitting adds an edit link to the thank-you page and receipt. With approvals, a request can be edited after it was rejected (it then goes back to approval), not while it waits or once it is approved.
- Receipts show the answers in the language the respondent used; hidden fields are left out. Receipts to an address typed into a form (not a signed-in account) are limited to 10 per hour per address, so a public form can't be used to send email to anyone.
- Every respondent gets a private receipt page showing their answers and approval status, printable to PDF.
- Admins can print any response (with signatures and approval history) and export all responses to Excel, CSV or JSON.
Access control
| Mode | Who can respond |
|---|---|
| Anyone with the link | Public, no sign-in |
| My organisation | Anyone who signs in with your Microsoft 365 tenant |
| Specific people & groups | Selected users, or members of selected Entra ID groups (nested groups included) |
You can also allow one response per person, make responses anonymous, set open and close dates, and cap the number of responses. Open and close times are in the organisation's timezone (Settings → General & brand).
People who may not open a form (not signed in, not on the list, form closed or not published) see only its title and description — the questions are not sent to their browser.
Responses & export
Results in the menu lists every form with its responses (newest activity first, last 7 days, approvals waiting); the form name at the top of a results page switches to another form. Each results page has two tabs that share the same filters (the Tips button explains them in short):
- Summary: key figures (responses with the change against the previous period, median and average time to complete, last response, approval rate), responses over 7 / 30 / 90 days or a year, approval status, and a card per question — shares and the most chosen answer, average / median / lowest / highest and distribution for numbers and ratings, Net Promoter Score with promoters, passives and detractors, the latest text answers. Click an answer's bar (or an approval status) to filter everything to the people who gave it; See all answers opens the table. Print prints the summary or saves it as PDF.
- Responses: a spreadsheet-style table. Click a column title to sort (again to reverse, a third time for the default order). The funnel next to it filters the column like Excel: Filter by values (tick values from a list with counts, which follows the other filters) or Filter by condition — text contains / is / starts with…, numbers = ≠ > ≥ < ≤ or between, dates on / before / after / between / in the last N days, and is empty / is not empty. Filters show as chips above the table; Add filter filters any column, including hidden ones. Columns chooses what is shown (remembered per form in your browser), number columns get totals (sum, average, lowest, highest) over all filtered rows, and 25 / 50 / 100 rows per page.
- Views save the filters, sorting and columns under a name (in your browser), e.g. “Approved, over 400 €”.
- Export (Excel, CSV, JSON) offers the filtered rows — in the table's order, with its columns — or all responses. CSV is an Excel-friendly UTF-8 file.
Click a row to see the full response with the respondent's department and manager, approvals (remind or change the approver) and a printable copy; ↑ / ↓ go to the previous / next response. The table can be compact or comfortable; row numbers and the first column stay visible while scrolling sideways.
PDF of a response
Every response can be saved as a PDF made on your server: the form's answers (tables, grids, signatures, consent with its time), the approval history and page numbers, with your organisation's name and logo. Nothing is sent to another service to make it.
- Download: the Download PDF button on a response (Results → a row), on the respondent's receipt page and on the approver's page. The REST API has
GET /api/v1/forms/{form}/responses/{id}/pdf(Business). - Email: in Access & settings → After submitting, Attach a PDF of the response (the email to your team) and Attach a PDF of their answers (the respondent's copy, in the language they answered in). The Send email, Outlook and Gmail integrations have Attach a PDF of the response.
- Storage: SharePoint, Google Drive, Dropbox and S3 integrations have Upload the response as PDF (
response.pdfin the response's folder); Save to a server folder writes it into the folder per response. Integrations saved before this option existed don't upload a PDF until you turn it on. - Scripts: Latin, Cyrillic, Greek, Hebrew and Arabic are built in (Arabic letters are joined and right-to-left text is wrapped in reading order). For Chinese, Japanese, Korean, Thai and other scripts, copy a TrueType font (
.ttf, regular weight) that has those characters tostorage/app/pdf-fonts/on the server, for example Noto Sans SC, JP or KR from Google Fonts; SnagBane uses it for the characters the built-in font lacks (up to 8 fonts). Scripts whose letters change shape when combined, such as Devanagari, show the letters but not their combined forms. - Server: PDFs need the PHP extensions
domandmbstring; signatures and a PNG logo also needgd(without it a signature shows as “Signed” with its time). The Docker image has all three; on shared hosting they are usually on — if not, your hosting provider can turn them on. Settings → System & updates shows Response PDFs.
Languages & multilingual forms
SnagBane ships in 33 languages: English, Español, Français, Deutsch, Italiano, Português (Brasil), Português (Portugal), Nederlands, Polski, Čeština, Slovenčina, Magyar, Română, Български, Ελληνικά, Srpski (latinica), Српски (ћирилица), Hrvatski, Slovenščina, Українська, Русский, Türkçe, Svenska, Dansk, Norsk bokmål, Suomi, العربية (right-to-left), हिन्दी, Bahasa Indonesia, 日本語, 한국어, 简体中文 and 繁體中文. Emails, validation messages, dates and numbers follow the language too.
- Settings → Languages: choose the default language, which languages people may pick, and whether the visitor's browser language is detected automatically. Every user can pick their own language in the sidebar or in Your profile; guests can switch on the sign-in page.
- Customise wording (same page): search any text in any language and replace it — e.g. “Submit” → “Send request”. Overrides are stored in the database and survive updates.
- Multilingual forms: in the builder open the Languages tab, set the form's main language and add translations. Every question, option, section, help text, confirmation message and button can be translated side by side. Respondents get their browser language automatically (or
?lang=dein the link) and can switch with the language picker. Answers are stored by option id, so results, charts and exports stay in one place; exports get a Language column. - Automatic translation (optional): connect DeepL, Azure AI Translator, Google Cloud Translation or a self-hosted LibreTranslate in Settings → Languages, then click “Translate missing automatically” in the form's Languages tab. Merge tags such as
{{name}}are protected. Changed source texts are flagged “needs review”. - Professional translators: export a form's texts as CSV, send them out, and import the file back.
- Adding another interface language: copy
lang/en.jsontolang/<code>.json, translate the values (keys stay English), add the language toconfig/locales.phpand runnpm run build.php scripts/i18n-extract.php --checkreports missing texts and broken placeholders; seescripts/TRANSLATOR_GUIDE.md.
Branding (white-label)
In Settings → General & brand you can set the site name, logo, favicon, primary and accent colours, login headline and footer text, and hide "Powered by SnagBane". Each form also has its own theme: accent colour, background, logo and cover image.
Integration catalog
Open a form's Integrations tab (or Automations & API for all forms), pick a connector, fill in the settings and click Send test. Every integration can run on new, edited, approved or rejected responses, and only when your conditions match (e.g. post to Telegram only when Priority is Urgent). Settings support merge tags like {{respondent.name}}, {{all_answers}} or any {{variable_name}}. Secrets are stored encrypted and never shown again; each run is logged with its result.
| Category | Connectors |
|---|---|
| Chat & notifications | Microsoft Teams, Slack, Telegram, Discord, Google Chat, Twilio SMS / WhatsApp |
| Email & marketing | Send email (custom, branded, with attachments), Outlook email and Gmail (from a mailbox you choose, also shared mailboxes and “Send mail as” addresses), Mailchimp, Brevo, MailerLite, Kit (ConvertKit), ActiveCampaign |
| Spreadsheets & databases | Google Sheets, Excel tables (auto-creates the table and columns), SharePoint lists (auto-creates the list and columns), Airtable, Notion |
| Files & storage | Dropbox, Google Drive, SharePoint / OneDrive, Amazon S3 / Cloudflare R2 / Wasabi / MinIO |
| CRM & custom | Dynamics 365 and Salesforce (Business), HubSpot, Custom HTTP request (any REST API) |
| Projects & tickets | Jira (Cloud and Data Center), ClickUp, Asana, monday.com, Trello |
| Calendar | Outlook calendar, Google Calendar |
| Accounts (onboarding & offboarding) | Microsoft 365 accounts, Google Workspace accounts, Okta accounts, SnagBane accounts (Enterprise plan, see Account integrations) |
| Local server | Local folder export (CSV + JSON lines + per-response folders with files), Local log file (JSON lines for SIEM) |
| Automation | Power Automate, Zapier, Make, n8n and signed webhooks (see below) |
Dynamic content and expressions
Click into a field that accepts answers: Add dynamic content or an expression appears under it (or press ⚡, or type {{). Dynamic content lists the form's questions, the respondent (name, email, department, manager), the response (number, date, link), the approval decision and the form, grouped and searchable — pick one and it shows as a named chip; the stored value is still a merge tag such as {{respondent.name}}. Expression combines them with functions, Power Automate style; the editor checks the expression as you type and saving refuses a broken one. Expressions run on your server when the integration runs; the result is escaped for its place (URL, JSON body, folder name, HTML), never the inputs.
| Kind | Functions |
|---|---|
| Text | concat, toUpper, toLower, trim, substring(text, start, length), replace(text, 'old', 'new'), length, coalesce (first value that isn't empty), json(text, 'path') (a value from JSON such as an HTTP reply: json(steps.5.body, 'data.items.0.id')) |
| Conditions | if(condition, 'when true', 'when false'), equals, contains, startsWith, endsWith, empty, greater, less, and, or, not — text comparisons ignore upper/lower case, numbers compare as numbers |
| Numbers | add, sub, mul, div, formatNumber(number, decimals) |
| Dates | utcNow('yyyy-MM-dd'), formatDateTime(date, 'dd.MM.yyyy HH:mm'), addDays(date, 7, 'yyyy-MM-dd'), addHours(date, 2), addMinutes(date, 30, 'HH:mm'), dayOfWeek(date) (0 = Sunday) — formats use yyyy MM dd HH mm ss, MMM/MMMM = month name, ddd/dddd = weekday, text in '…' stays as written; dates without a time zone are read in the organisation time zone |
To build an expression, open Expression, pick a function (it goes in with the cursor between the brackets), then switch to Dynamic content: while an expression is open, every item you click goes into it at the cursor — two in a row get a comma between them, e.g. concat(form.title, respondent.name). Insert (or Enter) adds it to the field as a purple fx chip; click the chip to change it. Backspace and Delete remove a whole chip.
Examples: {{if(equals(priority, 'High'), 'URGENT: ', '')}}{{form.title}} · {{toUpper(substring(respondent.name, 0, 1))}} · {{formatDateTime(submitted_at, 'd MMM yyyy')}} · {{coalesce(phone, 'no phone')}}. Texts go in single or double quotes ('it''s' for a quote inside); function names ignore case; a question is used by its variable name. A plain {{tag}} works as before, and an unknown tag is empty. Person, address and date-range questions also offer their parts: Requested by → Email is {{requested_by.email}} (also .name, .job_title, .department), an address has .street, .postal_code, .city, .region, .country, a date range .from and .to.
Steps that use each other's results
Integrations that create something return it for later steps: Jira (issue key, link), ClickUp, Asana and Trello (task or card ID and link), monday.com (item ID), Dynamics 365 and Salesforce (row / record ID), SharePoint list (item ID, link), Airtable (record ID), Notion (page ID, link), Google Sheets (row range), Outlook and Google calendar (event ID, link), Google Drive (folder ID, link), Dropbox and SharePoint files (folder), DocuSign, Acrobat Sign and Dropbox Sign (envelope / agreement / request ID), the account integrations when creating an account (sign-in name — never the password) and the Custom HTTP request (status code, reply body). Other integrations on the same form find them under Dynamic content → From earlier steps, e.g. a Teams or email message with “Ticket {{steps.12.key}} created: {{steps.12.url}}”. A step that uses another step's result always runs after it; if that step failed, it is skipped and its run says why. Results stay with the response, so an integration that runs after approval can use the ticket created when the form was sent.
Only when… as an expression, and “otherwise”
Under Only when…, Condition as an expression runs the integration only when the expression is true, e.g. greater(amount, 500) or and(equals(department, 'IT'), not(empty(steps.12.key))) — together with the rules above it, if any. When it isn't met, nothing is sent and nothing is logged as a failure; Send test always sends. Add “otherwise” step makes a switched-off copy with not(…) as its condition, for what should happen in the other case: check its settings and switch it on.
Run history and Run again
Open a run under Recent runs to see what it sent (each setting and mapped field as it was filled in — never secrets) and what came back. What was sent is kept for 30 days and goes when the response is deleted (also by data retention). Run again runs the step once more for the same response and event — e.g. after you fixed a setting — followed by the steps on the form that use its result; rules and conditions apply as on a real run.
Recipes
Start from a recipe on a form's integrations adds a ready-made flow in one click — e.g. Ticket + confirmation to the requester (Jira, then an email with the ticket link), After approval: Jira issue + Teams message, New starter: Microsoft 365 account + email to the manager, Approved leave in the calendar, Route by amount (an if / otherwise pair), Call any API, then use its reply. The steps come chained and switched off: fill in each one's settings, test and switch them on. A recipe that needs an app outside your plan, administrator rights or a person / number question says so.
Test the whole flow
Test the whole flow runs every step of one trigger once — also switched-off ones — in run order, with the latest response (or sample answers). Results really pass from step to step, rules and conditions apply, and each step shows what it sent and got back. It really sends (emails, tickets, accounts); each run is listed under the step's Recent runs as a test.
Apply to each (table rows)
On a form with a table question, Repeat for each row of runs the integration once per row (up to 50) — e.g. one Jira issue or one spreadsheet row per item. The row's columns are under Dynamic content → This row ({{row.item}}, {{row.number}}). Send test uses the first row; a repeated integration isn't retried as a whole, so rows that worked don't run twice.
On a schedule (digests)
Message integrations (email, Outlook mail, Gmail, Teams, Slack, Google Chat, Telegram, Discord, Twilio) and the HTTP request can run On a schedule instead of on a response: every day, week (pick the weekday) or month (pick the day, 1–28) at a time in the organisation time zone. They send a digest of the form for the period since the last one: {{digest.count}} new responses, {{digest.list}} (up to 25 lines), {{digest.pending}} requests waiting for approval, {{digest.period}} and {{digest.url}}. The first digest goes out at the first scheduled time after it is switched on; a time missed by more than 6 hours (scheduler not running) is skipped, never sent late. Needs the scheduler (cron schedule:run every minute) like reminders.
Flow map
On a form's integrations, Flow map shows what runs as a flow: one lane per trigger (New response, After approval…), steps in the order they run — side by side when independent, below the step whose result they use — and an If / Otherwise block for a step and its “otherwise” copy. Click a step to open it.
Account integrations (onboarding & offboarding)
Enterprise plan. These integrations change real accounts. They create accounts or switch off people's access as soon as they run, so SnagBane shows a red warning on them everywhere: in the catalog, on each configured integration and at the top of its settings.
- Microsoft 365 accounts — Switch off an account (offboarding): the account is switched off (
accountEnabled = false), every session is revoked so the person is signed out everywhere, licences are removed if you choose, and their SnagBane account is switched off too (on by default). Create an account (onboarding): a new accountfirst.last@your-domain(or a user name you set) with job title, department, manager, groups and licences, and a temporary password that must be changed at the first sign-in. The password is emailed only to the address you choose (e.g. the hiring manager) — it is never shown in SnagBane, the run log or the audit log. Needs the Create and switch off accounts permission (see Permissions explained). - Change an account: first and last name (display name follows), sign-in address (a new user name in the domain you typed), job title, department and manager — empty answers leave the current value. Switch an account back on: for someone who comes back.
- SnagBane accounts works the same way for SnagBane's own accounts: switch off (signed out at once), switch back on, change the name or email, or create an account. New accounts are always Respondent (no edit rights) and get an invitation email to choose a password; the plan's people limit applies. New and changed email addresses must be in one of the Allowed email domains you type, so an answer can never point an account at an outside address.
- Guardrails, the same for every account integration: accounts are never deleted, only switched off. Administrator accounts are never changed: nobody with a Microsoft Entra admin role (also through a role-assignable group) and no SnagBane owner, administrator or user & access admin. If SnagBane can't check whether someone is an administrator, it changes nothing. People are never added to groups that can hold admin roles. The domain of new accounts is typed by an administrator, never taken from an answer.
- Who and when: only administrators and owners can set them up, change them or see them in the catalog (user & access admins and editors can't); they run After approval by default, and nothing runs until I understand that this integration changes real accounts is ticked. Send test only checks the settings (and looks the person up for a real response) — it never creates or switches off an account. Every change is recorded in the audit log (
account.created,account.switched_off) with the form, response and integration. - Google Workspace accounts does the same in Google Workspace: Suspend (suspended and signed out of every session), Restore, Change (names, primary email in your domain, job title, department, manager) and Create (organisational unit, groups, a temporary password with change at next sign-in). Needs Create and switch off accounts under Settings → Google Workspace; with a service account, add its scopes (
admin.directory.user,admin.directory.user.security,admin.directory.group.member,admin.directory.rolemanagement.readonly) to domain-wide delegation. Super and delegated admins and anyone with an admin role assignment (also through a group) are never changed. - Okta accounts: Suspend (sessions and tokens cleared), Restore, Change (names, sign-in address, title, department, manager) and Create (groups, a temporary password that must be changed at first sign-in). Uses the API Services app from Settings → Okta → People who leave with the extra scopes
okta.users.manage,okta.groups.manageandokta.roles.read, and an admin role assigned to that app that covers your staff and can read admin roles — for example a custom admin role with the permissions to manage users, manage group members and View roles, resources, and admin assignments, with All Identity and access management resources in its resource set (these are permissions of the admin role, not API scopes; a role limited to a small resource set gets HTTP 403 when SnagBane checks for administrators — avoid Super Administrator). Accounts are suspended, never deactivated or deleted; anyone with an admin role, directly or through a group, is never changed. - Tips: use a person question for Whose account (exactly one address), require sign-in on the form, keep the approval step, and try it with a test account first.
Projects, tickets and calendars
Enterprise. These connectors turn a response or an approved request into work in the tools your teams already use.
- Jira, ClickUp, Asana, monday.com and Trello create one issue, ticket, task, item or card per run. The title and the description are templates (by default the form title with the respondent's name, and all answers); a link back to the response is added at the end of the description. Uploaded files are attached (Jira, ClickUp, Asana, Trello; up to 10 MB each, 100 MB for Asana). monday.com gets the answers as an update on the item instead.
- Field mapping: map answers to the tool's own fields by their name. Jira accepts field names or IDs (Priority, Due date, Assignee by email,
customfield_10010; select lists by option name). ClickUp and Asana map to custom fields by name (dropdown options by name); monday.com maps to board columns by their title (text, long text, numbers, status, dropdown, date, email, link, checkbox); a status or dropdown label the board doesn't have yet is added to it. An unknown field name makes the run fail with a clear message, so a typo never goes unnoticed; a value a field can't take (an option it doesn't have) leaves that field empty and the run log names it. - When they run: like every integration, on new, edited, approved or rejected responses, and only when your conditions match. For requests with approvals choose Approved, so work is only created for approved requests.
- Jira Data Center on your own network: an internal address must first be allowed under Settings → Security (private addresses are blocked by default).
- Outlook calendar and Google Calendar put an approved request into a calendar: the requester's own calendar, or a shared or team calendar (a mailbox you enter). They are set up by administrators only and run on Approved by default. The start and end come from the form's date questions (
{{start_date}}, optionally with a time question{{start_date}} {{start_time}}); dates without a time become all-day events, including the last day. Times are in the organisation's time zone (Settings → General & brand). “The requester” is always the signed-in person who sent the form — never an email address typed into it — so require sign-in on forms that use it. The Outlook calendar needs the Add approved requests to Outlook calendars permission (Calendars.ReadWrite, see Permissions explained); Google Calendar needs thecalendar.eventsscope in domain-wide delegation.
Setup guides
The same steps are shown in SnagBane above each integration's settings, in the user's language, with a link to the provider's official guide. Menu names are the provider's own (in English).
ActiveCampaign
- In ActiveCampaign, click Settings (gear icon) → Developer.
- Copy the API URL and the API key into the fields below.
- Optional: the numeric ID of the list to subscribe contacts to.
Official guide: help.activecampaign.com
Adobe Acrobat Sign (Business)
- Sign in to Acrobat Sign as an administrator and select API in the top menu (or Account if there is no API link) → API Applications. Select + to create an application with domain CUSTOMER, then select it → View / Edit and copy the application ID and secret.
- Select Configure OAuth for Application: enter
https://your-snagbane-address/integrations/adobe-sign/callbackas the redirect URI and enable agreement_read, agreement_write and agreement_send (modifier self, or account to send as any user), then save. - Paste the application ID and secret in SnagBane, and the address shown in your browser while signed in to Acrobat Sign (e.g.
https://secure.na4.adobesign.com/). Save, then click Connect Acrobat Sign and sign in as the account the agreements are sent from; you come back to SnagBane afterwards. Acrobat Sign needs an https address for SnagBane. - Keep “When does this run?” at After approval. Send test checks the connection without sending anything; the signers get Acrobat Sign’s email, and the signed PDF appears on the response.
Official guide: opensource.adobe.com/acrobat-sign/developer_guide
Airtable
- Open airtable.com/create/tokens and click Create new token. Name it e.g. SnagBane.
- Scopes: click Add a scope and choose data.records:write.
- Access: click Add a base and pick the base that holds your table (a base is an Airtable database — it has one or more tables, shown as tabs at the top). Click Create token, copy it (Airtable shows it only once) and paste it under Personal access token.
- Open your table in Airtable in the browser, copy the whole address from the address bar (it looks like airtable.com/app…/tbl…/viw…) and paste it under Table link. SnagBane takes the base and the table from it.
- Columns: under Field mapping, write each Airtable column name exactly as in the table on the left and pick the answer on the right. Or turn on “Also send every answer by question label” and name the columns like your questions. Answers without a matching column are skipped and named in the run log.
- Click Send test: a test record appears in the table.
Official guide: airtable.com
Amazon S3 / R2 / Wasabi / MinIO (Business)
- AWS: create the bucket, then IAM → Users → Create user (no console access needed).
- Attach a policy that allows s3:PutObject on arn:aws:s3:::your-bucket/*.
- Open the user → Security credentials → Create access key (“Application running outside AWS”) and copy both keys.
- Leave Endpoint empty and enter the bucket’s region.
- Cloudflare R2: R2 → Manage API tokens → Create API token with Object Read & Write for the bucket. Endpoint: https://<ACCOUNT_ID>.r2.cloudflarestorage.com, region: auto.
- Backblaze B2: Application Keys → Add a New Application Key, limited to the bucket, Read and Write (not the Master Application Key — the S3 API doesn’t accept it). Access key ID = keyID, secret = applicationKey; Endpoint = the bucket’s Endpoint (e.g. s3.eu-central-003.backblazeb2.com), the region is read from it.
- MinIO, Wasabi and others: use their S3 endpoint and access keys.
Official guide: docs.aws.amazon.com
Asana (Business)
- Open the Asana developer console (app.asana.com/0/my-apps) and create a personal access token. Copy it.
- Open the project the tasks should go to and copy its address from the browser.
- Optional: map answers to the project’s custom fields by their name.
- Save and click Send test.
Official guide: developers.asana.com
Brevo contacts
- In Brevo, open the account menu → Settings → SMTP & API → API Keys & MCP.
- Click Generate a new API key, name it, choose an expiry and copy it (it is shown only once).
- Optional: the IDs of the lists to add contacts to (Contacts → Lists), separated by commas.
Official guide: help.brevo.com
ClickUp (Business)
- In ClickUp, click your avatar in the upper-right corner → Settings, then Apps in the sidebar. Under API Token click Generate (or Regenerate) and copy the token (pk_…).
- In the Spaces sidebar, click ··· next to the list the tasks should go to (or right-click it) → Copy link, and paste the link below.
- Optional: map answers to the list’s custom fields by their name.
- Save and click Send test.
Official guide: developer.clickup.com
Custom HTTP request
- Take the URL and method from the service’s API documentation.
- Add headers such as Authorization: Bearer … (stored encrypted).
- Write the JSON body with merge tags; values are escaped automatically.
- Save and click Send test to see the reply.
Discord
- In Discord, open Server Settings → Integrations (you need the Manage Webhooks permission).
- Click Create Webhook (or Webhooks → New Webhook), give it a name and choose the channel.
- Click Copy Webhook URL and paste it below.
Official guide: support.discord.com
DocuSign (Business)
- Open the Apps and Keys page in DocuSign’s admin. Under My Account Information copy the User ID. Under Apps and Integration Keys add an app (or choose one → Actions → Edit) and copy its Integration Key.
- In that app, under Authentication select + GENERATE RSA and copy the private key; under Additional settings select + ADD URI, enter
https://your-snagbane-address/integrations/docusign/consentand select SAVE. - Paste the integration key, the user ID and the private key in SnagBane, choose Developer or Production and save. Click Send test: the first time it shows a consent link — open it, sign in as that user, allow access and click Send test again.
- Keep “When does this run?” at After approval. Send test checks the connection without sending an envelope; the signers get DocuSign’s email, and the signed PDF appears on the response.
Official guide: developers.docusign.com/platform/auth/jwt
Dropbox
- Open dropbox.com/developers/apps → Create app → Scoped access → App folder (or Full Dropbox).
- On the Permissions tab tick files.content.write and files.content.read, then click Submit.
- Copy the App key and App secret from the Settings tab into the fields below and save.
- Click Get authorisation code, allow access, then paste the code right away and click Connect — a code works only once and only for a few minutes.
- Click Send test: a folder with response.html and response.json appears in Dropbox (with an App folder app, under Apps → your app’s name).
Official guide: www.dropbox.com
Dropbox Sign (Business)
- Sign in at sign.dropbox.com, select your avatar → Settings → API, and select Reveal key (or Create key) next to API key.
- Paste the key in SnagBane and save. Turn on Test mode to try it first: test requests are not legally binding, and until the account has a paid Dropbox Sign API plan they can only be sent to the account owner’s own email address.
- Keep “When does this run?” at After approval. Send test checks the key without sending anything; the signers get Dropbox Sign’s email, and the signed PDF appears on the response.
Official guide: developers.hellosign.com/api/reference/authentication
Dynamics 365 (Business)
- Copy your environment URL: Power Platform admin center → Manage → Environments → your environment → Environment URL (e.g. https://contoso.crm4.dynamics.com).
- Choose which app registration signs in: the one SnagBane already uses for Microsoft 365, or another Entra app (its Directory ID, Application ID and a client secret). No API permission is needed.
- In the Power Platform admin center open the environment → Settings → Users + permissions → Application users → New app user, pick that app registration and give it a security role that can create rows in the table.
- Pick the table and map answers to its columns by their logical names (e.g. firstname, lastname, emailaddress1, subject).
- Save and click Send test — it checks the sign-in, the table and the column names without creating a row.
Official guide: github.com
Excel (Business)
- In Settings → Microsoft 365, turn on “Save responses to SharePoint / Excel” and connect an Excel account (Microsoft’s Excel API only works as a user).
- Open the workbook in Excel or SharePoint → Share → Copy link, and paste it below. The connected account must be able to edit it.
- Enter a table name. If the workbook has no table with that name, SnagBane adds a new sheet named after it and creates the table there (your other sheets, e.g. Sheet1, stay as they are). An existing table is used wherever it is.
- Optional, under Field mapping: choose the columns yourself — column name on the left, what goes in it on the right (merge tags such as {{employee}}). Without a mapping every question gets its own column.
- Save and click Send test.
Google Calendar (Business)
- In Settings → Google Workspace, tick “Add approved requests to Google Calendar” and save.
- In the Google Admin console → Security → Access and data control → API controls → Manage domain-wide delegation, add the scope https://www.googleapis.com/auth/calendar.events to SnagBane’s service account.
- Choose whose calendar. For a shared calendar, enter a mailbox that can edit it and the calendar’s ID (Calendar settings → Integrate calendar).
- Keep “Run on” at Approved, so only approved requests reach the calendar. Save and click Send test.
Official guide: developers.google.com
Outlook email (Business)
- In Settings → Microsoft 365, click “Re-run setup / change permissions”, tick “Send email via Microsoft 365 mailbox” (Mail.Send) and sign in once as a Global Administrator.
- Under “Send from”, enter a user or shared mailbox of your organisation, e.g. hr@company.com. The email comes from that mailbox and a copy goes to its Sent Items.
- Optional: limit which mailboxes SnagBane may send from with Exchange Online RBAC for Applications.
- People who aren’t administrators can only use mailboxes listed under Settings → Security.
Official guide: learn.microsoft.com
Gmail (Business)
- In Settings → Google Workspace, tick “Send email from a Workspace mailbox (Gmail API)” and save.
- With a service account: in the Google Admin console → Security → Access and data control → API controls → Manage domain-wide delegation, add the scope https://www.googleapis.com/auth/gmail.send to SnagBane’s service account.
- Under “Send from”, enter the account’s address. To send from another address (an alias or a group), first add it in that account’s Gmail → Settings → Accounts → “Send mail as”, then enter the account under “Account that sends” and the other address under “Send from”.
- With “Connect with Google”, email goes out only from the connected account and its “Send mail as” addresses.
- People who aren’t administrators can only use mailboxes listed under Settings → Security.
Official guide: developers.google.com
Google Workspace accounts (Enterprise plan)
- In Settings → Google Workspace, tick “Create and switch off accounts” and save. With a service account, add its scopes in the Admin console → Security → Access and data control → API controls → Manage domain-wide delegation; with “Connect with Google”, connect again as a super administrator.
- Use a new-starter or leaver form with an approval step. Choose what this integration does and insert the form’s questions.
- New accounts get a temporary password that must be changed at first sign-in. It is emailed only to the address under “Send the temporary password to” — never shown in SnagBane or its logs.
- Keep “Run on” at After approval, tick the confirmation and click Send test: it only checks the settings and changes nothing.
Official guide: developers.google.com
Google Chat (Business)
- Open the space in Google Chat in a browser (webhooks can’t be added in the mobile app).
- Click the arrow next to the space name → Apps & integrations → Add webhooks.
- Enter a name and click Save, then click More (⋮) → Copy link and paste it below.
- Option missing? Your Google Workspace admin has to allow incoming webhooks.
Official guide: developers.google.com
Google Drive (Business)
- Set up the service account once in Settings → Google Workspace, with the Google Drive API enabled.
- Service accounts have no storage of their own: open a Shared Drive → Manage members and add the service account email as Content manager.
- Open the folder in that Shared Drive and paste its link below.
- Or fill in “Upload as user (domain-wide delegation)” to save into that person’s My Drive.
Official guide: support.google.com
Google Sheets (Business)
- Set up the service account once in Settings → Google Workspace, with the Google Sheets API enabled.
- Open the spreadsheet → Share and add the service account email as Editor.
- Paste the spreadsheet link below. The tab is added if it doesn’t exist.
Official guide: developers.google.com
HubSpot
- In HubSpot, open Development → Keys → Service keys (or Settings → Integrations → Service keys) and create a key. You need Developer tools access.
- Name it and add the scope crm.objects.contacts.write.
- Copy the key and paste it below. A token from an existing legacy private app works too.
- Map answers to HubSpot contact properties below.
Official guide: developers.hubspot.com
Jira (Business)
- Jira Cloud: open id.atlassian.com/manage-profile/security/api-tokens → Create API token → name and expiry → Create → Copy (shown only once). Enter it with the email you sign in to Atlassian with.
- Jira Data Center or Server (8.14 or later): your avatar → Profile → Personal access tokens → Create token. Leave the email empty.
- Enter your Jira address (e.g. https://your-company.atlassian.net), the project key (the letters before issue numbers, e.g. IT in IT-42) and the issue type.
- Optional: map answers to Jira fields by their name or ID (Priority, Due date, Assignee, customfield_10010…).
- Save and click Send test.
Official guide: developer.atlassian.com
Kit (ConvertKit)
- In Kit, open Settings → Developer and add a new API key (v4).
- Copy the key (it is shown only once) and paste it below.
- Optional tag ID: open the tag under Grow → Subscribers; the number in the page address is its ID.
Official guide: help.kit.com
Local folder export
- Files are written on the server below the export root shown under Sub-folder.
- With Docker, mount that folder as a volume or network share to reach the files.
- Choose what to write: responses.csv, responses.jsonl and/or a folder per response.
Local log file
- Each event is appended as one JSON line to storage/logs/forms/<form>.log.
- Point your log shipper (Filebeat, Fluent Bit, Vector, Splunk forwarder…) at storage/logs/forms/*.log.
- Turn off the Include answers option for privacy-sensitive forms.
Mailchimp
- In Mailchimp, click your profile icon → Profile → Extras → API keys.
- Click Create A Key, name it (e.g. SnagBane), click Generate Key and copy it — Mailchimp shows it only once. Paste it under API key; it ends with your data center, e.g. -us17.
- Audience ID: click Audience, pick the audience if you have several, then More options → Audience settings, and copy the Audience ID.
- Double opt-in on: new people get a confirmation email and are added once they click it (status Pending until then). Off: they are added right away — only when they agreed to receive emails.
- Name goes to the audience’s First name field (FNAME). Other answers: under Field mapping, write the merge tag on the left (Audience → More options (⋯) → Audience fields and merge tags, e.g. PHONE) and pick the answer on the right. Tags (optional) can use answers too, e.g. {{department}}.
- Click Send test: it checks the key, the audience and the merge tags without adding anyone.
Official guide: mailchimp.com
MailerLite
- In MailerLite, open Integrations → MailerLite API → Use.
- Click Generate new token, name it and copy it right away (it is shown only once).
- Group IDs are listed in the Groups section of the same page; separate several with commas.
Official guide: www.mailerlite.com
Microsoft 365 accounts (Enterprise plan)
- In Settings → Microsoft 365, click “Re-run setup / change permissions”, tick “Create and switch off accounts” and sign in once as a Global Administrator.
- Use a new-starter or leaver form with an approval step. Choose what this integration does and insert the form’s questions.
- New accounts get a temporary password that must be changed at first sign-in. It is emailed only to the address under “Send the temporary password to” — never shown in SnagBane or its logs.
- Keep “Run on” at After approval, tick the confirmation and click Send test: it only checks the settings and changes nothing.
Official guide: learn.microsoft.com
Microsoft Teams (Business)
- In Teams, click ⋯ next to the channel or chat and choose Workflows.
- Pick “Send webhook alerts to a channel” (or “Send webhook alerts to a chat”).
- Check the team and channel, click Save and copy the webhook link.
- Paste it below, save, then click Send test.
Official guide: support.microsoft.com
monday.com (Business)
- In monday.com, click your profile picture → Developers. In the Developer Center click API token → Show and copy it.
- Open the board the items should go to and copy its address from the browser.
- Optional: map answers to the board’s columns by their title (text, numbers, status, dropdown, date, email, link, checkbox).
- Save and click Send test.
Official guide: developer.monday.com
Notion
- In Notion, open Settings → Connections and turn on Developer Mode (workspace owners only).
- In the developer section of the sidebar, click + New connection, name it (e.g. SnagBane) and pick the workspace.
- Back in Settings → Connections, click ••• next to the connection, copy its token (it starts with ntn_) and paste it under Integration secret.
- Share the database with the connection: open the database as a full page, click ••• at the top right → Add connections and pick your connection. SnagBane can only write to databases shared this way.
- Copy the database link (••• → Copy link, or the browser’s address bar) and paste it under Database URL or ID.
- Page title fills the database’s title column. Other columns: under Field mapping, write the column name exactly as in Notion on the left and pick the answer on the right. Text, number, select, multi-select, status, date, checkbox, email, phone and URL columns are filled; people, relation and file columns can’t be.
- Click Send test: a new page appears in the database.
Official guide: developers.notion.com
Okta accounts (Enterprise plan)
- Connect the Okta API first under Settings → Okta → People who leave. Then in the Okta Admin Console open that API Services app → Okta API Scopes and grant okta.users.manage, okta.groups.manage and okta.roles.read.
- In the same app, open Admin roles and assign a role that covers your staff and can read admin roles — for example a custom admin role (Security → Administrators → Roles) with the permissions to manage users, manage group members and “View roles, resources, and admin assignments”, on a resource set with your staff, their groups and “All Identity and access management resources”. These are permissions of the admin role, not API scopes. Avoid Super Administrator.
- Use a new-starter or leaver form with an approval step. Choose what this integration does and insert the form’s questions.
- Keep “Run on” at After approval, tick the confirmation and click Send test: it only checks the settings and changes nothing.
Official guide: developer.okta.com
Outlook calendar (Business)
- In Settings → Microsoft 365, click “Re-run setup / change permissions”, tick “Add approved requests to Outlook calendars” (Calendars.ReadWrite) and sign in once as a Global Administrator.
- Optional: limit which mailboxes SnagBane may write to with Exchange Online RBAC for Applications.
- Choose whose calendar, and insert the form’s date questions as start and end.
- Keep “Run on” at Approved, so only approved requests reach the calendar. Save and click Send test.
Official guide: learn.microsoft.com
Salesforce (Business)
- In Salesforce Setup search for External Client App Manager → New External Client App (Distribution State: Local).
- Under API (Enable OAuth Settings) select Enable OAuth, enter any https callback URL (it isn’t used) and add the scope “Manage user data via APIs (api)”. Under Flow Enablement select Enable Client Credentials Flow.
- On the app’s Policies tab → Edit → OAuth Policies: set Run As to an integration user that may create the records. Save.
- On the Settings tab open OAuth Settings → Consumer Key and Secret and copy both. Your address is your My Domain URL (Setup → My Domain), e.g. https://acme.my.salesforce.com.
- Pick the object and map answers to its fields by API name (e.g. LastName, Company, Email, Description). Save and click Send test — it checks everything without creating a record.
Official guide: docs.aws.amazon.com
Send email
- Email is sent through the service set up in Settings → Email.
- Enter recipients separated by commas; a merge tag such as {{email}} sends to an address from the form.
- Add conditions below to route by answer, e.g. a different team per department.
SnagBane accounts (Enterprise plan)
- Use a form with a person or email question for the person, and an approval step.
- Choose what this integration does and insert the form’s questions. New and changed email addresses must be in one of the domains you type here.
- Keep “Run on” at After approval, tick the confirmation and save. Send test only looks the account up, it changes nothing.
SharePoint / OneDrive files (Business)
- In Settings → Microsoft 365, turn on “Save responses to SharePoint / Excel” and grant admin consent.
- Enter the site address; files are saved in the site’s Documents library.
- Choose a folder; each response gets its own sub-folder.
- Save and click Send test to check the permissions.
SharePoint list (Business)
- In Settings → Microsoft 365, turn on “Save responses to SharePoint / Excel” and grant admin consent.
- Enter the site address, e.g. https://contoso.sharepoint.com/sites/HR.
- Choose a list name. With automatic creation on, the list and its columns are created for you.
- Save and click Send test to check the permissions.
Slack (Business)
- Go to api.slack.com/apps and create an app (From scratch) in your workspace.
- Open Incoming Webhooks and turn on Activate Incoming Webhooks.
- Click Add New Webhook to Workspace, pick the channel and click Authorize.
- Copy the webhook URL (https://hooks.slack.com/services/…) and paste it below.
Official guide: docs.slack.dev
Telegram
- In Telegram, message @BotFather and send /newbot.
- Choose a name and a username ending in “bot”, then copy the token BotFather sends you.
- Add the bot to your group, or as an administrator of your channel.
- Chat ID: @channelname for a public channel. For a group or a private channel, send a message in it, open https://api.telegram.org/bot<token>/getUpdates and copy chat → id (a negative number).
Official guide: core.telegram.org
Trello (Business)
- Trello gives API keys only to an app: open trello.com/apps/admin and open your app, or click New (app name, Workspace, email, author; it doesn’t need Power-Up capabilities).
- In the app: Authorization → Trello Auth. Copy the API key (not the Secret).
- On the same page, click Token in the text on the right (“manually generate a Token”) → Allow, and copy the token (it starts with ATTA).
- Paste the board’s address (trello.com/b/…) and the name of the list the cards should go to.
- Save and click Send test.
Official guide: developer.atlassian.com
Twilio SMS / WhatsApp
- Sign in to the Twilio Console (console.twilio.com). The Account SID and Auth token are under Account Info.
- From: a Twilio phone number you own (+1555…), or whatsapp:+… for a WhatsApp sender. Your own mobile number can’t be the sender, even when verified in Twilio.
- New Twilio trial accounts only send from the Console’s “Try out” pages: to send from SnagBane, upgrade the account (pay as you go).
- To try WhatsApp: Console → Messaging → Try WhatsApp, and join the sandbox from your phone first.
- To: a number in international format, or a merge tag such as {{phone}}.
Official guide: help.twilio.com
Email providers
Settings → Email supports SMTP, Microsoft 365 (Graph), Google Workspace (Gmail API), SendGrid, Mailgun (US/EU), Postmark, Brevo, Resend and Amazon SES — all via their official HTTPS APIs, no extra software. Use Log only while testing, or Mailpit in the Docker stack.
Power Automate (Business)
Power Automate Premium is needed for both options below (the HTTP trigger and the custom connector are Premium in Power Automate): everyone who builds or runs these flows needs a Power Automate Premium licence — Microsoft 365 plans don't include it. Without Premium, use SnagBane's built-in Microsoft 365 integrations (Teams, Outlook, SharePoint, Excel, approvals in Teams), which need no Power Automate licence at all. The custom connector (option B) calls SnagBane from Microsoft's cloud while a flow is built and saved, so SnagBane's address must be reachable from there (a firewall or a Cloudflare browser check in front of SnagBane stops it — see API behind a firewall). Responses are then sent by SnagBane to the flow. A saved flow is listed under Connected flows in the form's Automations, where Test sends it a response; a saved flow that isn't listed there never reached SnagBane — turn it off and on again in Power Automate.
Option A: HTTP trigger (2 minutes)
Licence: When an HTTP request is received is a Premium trigger — the flow's owner needs a Power Automate Premium licence (the free Microsoft 365 plan doesn't include it). People who send the form need none.
- Create a flow with the trigger When an HTTP request is received and set Who can trigger the flow? to Anyone. New flows start with Any user in my tenant, which needs a Microsoft sign-in token that SnagBane doesn't send (the flow answers 401). With Anyone the flow's URL works like a password: keep it private — every request is signed (
X-SnagBane-Signature-V2) so the flow can check it. - In SnagBane, open the form, go to Connect apps and click Copy JSON schema. Paste the schema into the trigger's Request Body JSON Schema.
- Save the flow and copy its URL. Back in SnagBane, click Power Automate, paste the URL, click Create automation and then Test on it.
Your answers are available as response.fields.<variable_name>, and the respondent as response.respondent.name/email/department/job_title/manager/manager_email.
Licence: a custom connector (option B) is Premium too — everyone who builds or runs a flow with it needs a Power Automate Premium licence. The Teams Workflows webhook used by SnagBane's Teams integration needs no Premium licence.
Option B: Custom connector (recommended for organisations)
- In SnagBane, open Automations & API and copy the connector address (
https://your-domain/integrations/power-automate/snagbane.swagger.json). - In Power Automate (make.powerautomate.com): More → Discover all → Custom connectors → New custom connector → Import from URL. Name it “SnagBane”, paste the address, click Import, then Create connector. Change nothing: the host, base URL and API-key sign-in are already in the file.
- On the same page in SnagBane click Create a token for Power Automate and copy the token. When Power Automate asks for the API key (first time you use the connector), paste it —
Bearerin front is optional. Give the connection a name you'll recognise. - In a flow, choose the trigger When a response is submitted (SnagBane) and your form. Every question is then its own dynamic content.
Saving the flow connects it: the form's Connect apps → Automations tab (and Automations & API) lists it under Connected flows, with its delivery log; the audit log records api.hook_subscribed (or api.hook_refused with the reason). Turning the flow off or deleting it removes it again.
Flow makers now get the trigger When a response is submitted (pick the form; every question is then its own dynamic content, named after the question, next to the respondent's name, email, department and manager) and the actions List forms, Get responses and Get a response. Connectors imported before 2.25.0 have an older trigger without per-question fields: import the file again (Update connector) to get the new one; existing flows keep working. SnagBane registers the flow's callback URL automatically and removes it when the flow is deleted.
Zapier, Make and n8n
Make and n8n work in every edition; Zapier is part of Business.
All three receive the same JSON as a webhook. In SnagBane open the form → Automations → pick the service; the same three steps are shown there.
| Service | In the service | In SnagBane |
|---|---|---|
| Zapier | Create a Zap with the trigger Webhooks by Zapier → Catch Hook and copy the webhook URL Zapier shows. | Paste it as Catch Hook URL, create the automation, click Test, then Test trigger in Zapier: the answers appear as fields for the next steps. |
| Make | Create a scenario and add the Webhooks → Custom webhook module; click Add and copy the webhook address. | Paste it as Webhook URL, create the automation and click Test: Make learns the data structure (for an automation that runs after approval, Test sends an approved request with approval.by and approval.comment). Then add the next modules and turn the scenario on — until it is on, Make refuses deliveries with “There is no scenario listening for this webhook” (HTTP 410), shown in the automation's Log. |
| n8n | Add a Webhook node with the HTTP method POST; copy its Production URL (not the Test URL) and activate the workflow. | Paste it as Production webhook URL, create the automation, click Test and check the execution in n8n. |
Failed deliveries are retried; every attempt is listed under the automation's Log. To check that a call really came from SnagBane, verify the signature as described under Webhooks & signatures (n8n and Make can do it with a code step).
Microsoft Teams notifications
In Teams, open the chat or channel, then ⋯ → Workflows and pick “Send webhook alerts to a chat” or “Send webhook alerts to a channel”. Finish the wizard and copy the webhook URL. In SnagBane, add the Microsoft Teams integration (form → Integrations, or Automations & API for all forms) and paste the URL. Each response then posts an Adaptive Card with the answers and an Open in SnagBane button.
Webhooks & signatures
SnagBane POSTs JSON for the events response.created, response.updated, approval.requested, approval.approved, approval.rejected, form.published and form.closed. A failed delivery is retried up to 3 times, and every attempt is logged. You can re-send any delivery from the log.
POST /your-endpoint
X-SnagBane-Event: response.created
X-SnagBane-Signature: sha256=5d7f…
{ "event": "response.created", "form": {"id": 2, "title": "…"},
"response": { "id": 91, "respondent": {"name": "Adele Vance", "email": "…"},
"fields": {"nps": 9, "rate_our_support": 5}, "answers": [ … ] } }
Every request also carries X-SnagBane-Timestamp and X-SnagBane-Signature-V2 (HMAC-SHA256 of timestamp.body), so receivers can reject replayed requests, and an X-SnagBane-Delivery id that stays the same across retries. Verify it in PHP:
$body = file_get_contents('php://input'); $ts = $_SERVER['HTTP_X_SNAGBANE_TIMESTAMP'] ?? '';
$valid = ctype_digit($ts) && abs(time() - (int) $ts) <= 300
&& hash_equals('sha256='.hash_hmac('sha256', $ts.'.'.$body, $secret), $_SERVER['HTTP_X_SNAGBANE_SIGNATURE_V2'] ?? '');
Examples for Node.js, Python and C#, and the full event list, are in the API reference.
REST API (Business)
Read forms and responses from scripts and other systems with a personal API token (Automations & API → Personal API tokens). Base URL: https://your-domain/api/v1, header Authorization: Bearer sb_…, 120 requests per minute. A token acts as the person who made it. Administrators can switch the list to Everyone's to see all tokens with their owner and revoke forgotten ones (logged in the audit log).
| Method & path | Description |
|---|---|
GET /me | The token's user |
GET /forms | List forms |
GET /forms/{id} | Form with its questions (key, label, type, options) |
GET /forms/{id}/responses?since=&top=&page= | Paginated responses, newest first |
GET /forms/{id}/responses/{responseId} | One response |
POST /hooks {form_id, url, event} | Subscribe a REST hook (returns its id and signing secret) |
DELETE /hooks/{id} | Unsubscribe |
GET /hooks/sample/{form} | Sample payload for mapping fields |
curl https://forms.example.com/api/v1/forms/2/responses?top=10 -H "Authorization: Bearer sb_xxx"
Full reference with examples (curl, PowerShell, Python), errors, pagination, answer formats and webhook verification: REST API reference →
Let Power Automate, Zapier and other cloud tools reach the API
The Power Automate custom connector, Zapier and other cloud tools call SnagBane's REST API from their own servers. If something in front of SnagBane protects it (Cloudflare, AWS WAF, Azure Front Door or Application Gateway, a company firewall), it must let these calls through, or the connector can't list forms, a flow can't connect and nothing is delivered. Browser checks (“Just a moment…”, CAPTCHA, JavaScript challenges) can't be solved by these tools, so they fail even though the API itself works.
Signs it's blocked: Power Automate shows Could not retrieve values… <!DOCTYPE html>… Just a moment…, a test in Zapier fails with an HTML page, or a saved flow isn't listed under Connected flows in the form's Automations. It can work one moment and fail the next: bot checks decide per request.
What to allow
- In: HTTPS requests to
https://your-domain/api/v1/(everything below it). Only this path — the rest of SnagBane keeps its protection. Every API call must carry a SnagBane API token (Authorization: Bearer sb_…); without a valid token SnagBane answers 401, and each token can do only what its owner can. - Narrower, if your firewall works with addresses: Power Automate connectors call from Microsoft's connector addresses, published as the Azure service tag AzureConnectors (Microsoft: IP address configuration for Power Automate). For other tools, use the addresses they publish; if they don't publish fixed ones, allow by path only.
- Out (from the SnagBane server): responses are sent by SnagBane to the flow, so the server must be allowed to reach
*.api.powerplatform.comand*.logic.azure.comover HTTPS (the domains Microsoft lists for flows with the HTTP trigger), or the tool's own webhook address.
Cloudflare
- Bot Fight Mode (Free plan) can't be skipped with WAF custom rules or Page Rules — Cloudflare's docs say Skip, Bypass and Allow have no effect on it. Either turn it off (Security → Settings, filter Bot traffic, Bot Fight Mode off) or use Super Bot Fight Mode (Pro and higher plans), which can be skipped by a rule. Cloudflare also notes it doesn't trigger when an IP Access rule matches first, e.g. an Allow rule for the AzureConnectors ranges.
- Super Bot Fight Mode, managed rules, rate limiting, Browser Integrity Check, Security Level: Security rules → Create rule → Custom rules, expression
starts_with(http.request.uri.path, "/api/v1/")(in the builder: URI Path starts with/api/v1/), action Skip, and tick only what stops the calls: All Super Bot Fight Mode rules, and if needed Browser Integrity Check and Security Level. Leave the rest (managed rules) on unless they're what blocks. - I'm Under Attack mode: a Configuration Rule for the same path can turn I'm Under Attack off for
/api/v1/only. - Check: Security → Analytics → Events shows which service challenged a request (e.g. Bot Fight Mode).
AWS (AWS WAF on CloudFront, Application Load Balancer, API Gateway)
- Bot Control: add a scope-down statement to the Bot Control rule group so it doesn't evaluate
/api/v1/— a NOT statement around URI path starts with/api/v1/. AWS's own guidance suggests scoping Bot Control away from authenticated-only parts, which the token-protected API is. - Challenge and CAPTCHA rules: don't apply them to
/api/v1/. AWS notes API clients can't complete a Challenge, so those requests are effectively blocked. - Managed rules that block a real API call (false positive in the WAF logs): AWS recommends a label-based exception for that one rule, not a custom Allow rule (Allow skips every later rule).
- Security groups / network ACLs in front of SnagBane must accept HTTPS from the calling tool (the AzureConnectors ranges for Power Automate), and allow the SnagBane server outbound HTTPS to the domains above.
Azure (Front Door, Application Gateway, App Service)
- Front Door WAF / Application Gateway WAF: a custom rule with a low priority number, match RequestUri BeginsWith
/api/v1/, action Allow. Microsoft's docs note that Allow ends evaluation, so the managed rules and bot rules no longer check these requests — add a second condition RemoteAddr IPMatch with the AzureConnectors ranges to keep it narrow. - App Service access restrictions: add a rule of type Service Tag → AzureConnectors (Microsoft supports all public service tags there).
- Network security groups / Azure Firewall: allow inbound HTTPS from the service tag AzureConnectors; Microsoft recommends service tags so you don't have to update IP ranges by hand.
Other firewalls and reverse proxies (F5, Fortinet, Imperva, Akamai, nginx, IIS…)
- Add an exception for the path
/api/v1/to bot protection, JavaScript or CAPTCHA challenges and “human” checks. - Make sure the proxy passes the
Authorizationheader through to SnagBane unchanged. - On-premises firewalls: Microsoft publishes the current AzureConnectors ranges through the Service Tag Discovery API.
After the change, open the connector's trigger in Power Automate again (the form list should load every time), save the flow, check that it's listed under Connected flows and click Test there.
Security features
Found a security problem? Please report it privately as described in the vulnerability disclosure policy (also in security.txt). What SnagBane sends out of your server, its architecture and the data processing addendum for support data are on the security page.
- Two-factor authentication for SnagBane password accounts (Your profile → Two-factor authentication): an authenticator app (TOTP — the setup key can be copied into a password manager such as 1Password, Bitwarden or LastPass) and/or FIDO2 security keys and passkeys (YubiKey over USB/NFC, Windows Hello, Google Password Manager, iCloud Keychain), up to 10 keys, with one set of recovery codes. People can add, change and remove methods themselves; changes need the second step again (never the password alone), and the strongest method is asked first at sign-in. Administrators can require someone to set up two-step sign-in again. Optionally required for admins (one method must then stay). Microsoft, Google, Okta and SAML sign-ins use their own provider’s MFA. Passkeys are tied to the site address — if the address changes, add the keys again (the app and recovery codes keep working). Step by step: Two-step sign-in & passkeys.
- Account lockout after repeated failed logins, generic error messages, strong password policy, bcrypt hashing, CSRF protection and session-fixation protection.
- Sessions end after the inactivity limit in Settings → Security → Session lifetime (8 hours by default). Keep me signed in on the sign-in page (off unless ticked) lasts the number of days set under Keep me signed in (days) — 14 by default, at most 90, 0 hides the option; the end date is checked by the server, so a remember cookie can't outlive it. Sign-ins with Microsoft, Google, Okta or SAML are never remembered: after the inactivity limit people sign in again through their provider (usually one click).
- Forgot your password? (sign-in page, needs email; switch under Settings → Security): a one-time link valid for an hour, only its hash is stored; the answer is the same whether or not an address has an account; at most one email per address every 2 minutes and 5 a day, 10 requests and 10 wrong links per IP address an hour; using the link never signs anyone in (two-step verification still applies), signs the account out everywhere and sends a “your password was changed” email. Accounts that sign in with Microsoft, Google, Okta or SAML get a reminder of how they sign in instead of a link.
- Strict Content-Security-Policy with per-request nonces, clickjacking protection (public forms stay embeddable), HSTS on https, nosniff and referrer policy.
- Uploads: extension allow-list, content sniffing, blocked scripts/executables/SVG/HTML and double extensions, random file names, private storage outside the web root.
- SSRF protection for all webhooks and integrations (private/metadata addresses blocked, DNS-rebinding safe), with an admin allow-list for trusted internal hosts.
- All secrets (Microsoft, Google, mail, integration credentials) encrypted with your APP_KEY; API tokens, approval links, edit and resume links stored only as hashes.
- Server-side re-validation of every answer, branching path and calculation; answer keys never leave the server; spreadsheet formula-injection protection in CSV, Excel and Google Sheets exports.
- Full audit log of sign-ins, admin actions, exports and approvals.
Two-step sign-in & passkeys
People who sign in to SnagBane with an email and password can add a second step under Your profile → Two-factor authentication. People who sign in with Microsoft, Google, Okta or SAML don't need it: their provider's own MFA and Conditional Access apply. One person can use several methods at once and change them at any time; one set of recovery codes works for all of them.
| Method | What you need | Good for |
|---|---|---|
| Authenticator app | Microsoft or Google Authenticator, Authy, or a password manager with one-time codes (1Password, Bitwarden, LastPass…) | Works everywhere; the code also confirms changes to your sign-in methods |
| Passkey on this computer | Windows Hello (PIN, fingerprint or face), Touch ID on a Mac | One click at every sign-in on your own computer |
| Passkey on your phone | Google Password Manager on Android, iCloud Keychain on iPhone, or a password manager that stores passkeys | Signing in on any computer with your phone nearby |
| Security key | A FIDO2 key such as a YubiKey (USB or NFC) | Shared or locked-down computers, people who must not use a phone |
Authenticator app (and password managers)
- Open Your profile and click Set up next to Authenticator app.
- Scan the QR code with the app. Using a password manager? Click the copy button next to the setup key and paste it into the item's one-time code (TOTP) field.
- Type the 6-digit code it shows and click Turn on. Save the recovery codes that appear.
Change app works the same way; your current app keeps working until the new one is confirmed. Remove takes the app off (security keys stay).
Security keys and passkeys (FIDO2)
- In Your profile → Security keys and passkeys, type a name you will recognise (“Work laptop”, “YubiKey”, “Phone”) and click Add security key or passkey.
- Your browser asks where to save it:
- Windows Hello — confirm with your PIN, fingerprint or face. The passkey stays on this computer.
- Google Password Manager — in Chrome signed in with your Google account (or on an Android phone) the passkey is saved to the account and synced to your other devices signed in with it.
- Your phone — choose Use a phone or tablet / A different device and scan the QR code with your Android phone or iPhone; the passkey is saved on the phone (Bluetooth must be on on both devices).
- Security key — insert the key (or hold it to the phone for NFC) and touch it; enter its PIN if it has one.
- The key appears in the list with the date it was added and last used. Add a second key or the authenticator app as a backup.
Signing in: after the password SnagBane asks for the strongest method you have — a security key or passkey if you added one (the browser asks for it right away), otherwise the code from your app. Can’t use the key? Click Can’t use your security key? and enter a code from the app or a recovery code. The password never replaces the second step.
Changes, recovery codes and admins
- Once a second step is on, every change (new app, adding or removing a key, new recovery codes) asks for that second step again — your security key, a code from the app or a recovery code; the password alone is not enough. One confirmation covers the next 5 minutes, and so does signing in.
- Keep your recovery codes. They are the only way in when your key or phone is lost — the password can’t be used instead. New recovery codes replaces all old codes; each code works once. Keep them in a password manager or somewhere safe.
- When Settings → Security → Require two-factor authentication for admins is on, admins can switch methods freely but can't remove the last one.
- Lost every method and the recovery codes? An administrator opens Settings → Users & roles, the person’s ⋯ menu and Require new two-step setup: the app, keys, passkeys and recovery codes are removed, and the person can open only their profile until they set up a new method at the next sign-in. Owners can do this for anyone, other owners included; administrators and user & access admins for people up to their own role. Accounts that sign in only with Microsoft, Google, Okta or SAML keep their provider’s MFA. If the password may be known to someone else too, reset it as well.
- Passkeys belong to the address SnagBane runs on (for example
forms.company.com). If that address changes, add the keys again; the authenticator app and recovery codes keep working. Passkeys needhttps://(orlocalhostfor a local test).
The key isn't offered or nothing happens? Make sure you use the address shown in your browser for SnagBane (not an old or internal name), that the browser is up to date, and — for a phone — that Bluetooth is on on both devices. A message “This key is already added” means the key is already in your list.
Data retention & GDPR
Enterprise plan. Keep personal data only as long as you need it. Settings → Security → Data retention:
- Delete old responses automatically — on/off (off by default), and Keep responses for 30 days … 10 years or any number of days, counted from the day a response was submitted.
- Every night (02:31, with the scheduler) SnagBane deletes responses older than that: answers, uploaded files, signatures and their approval history. Requests still waiting for approval are never deleted. Integration runs keep their status line but lose the link to the response; webhook delivery logs (which contain answers) are removed after 90 days anyway.
- The card shows what is due for deletion now per form, and Delete now runs the same deletion at once (after a confirmation — it can’t be undone; export first if you need a copy).
- Each form can follow the organisation policy, keep its responses, or delete after its own number of days (form → Access & settings → Data retention). Switch off Let form editors choose another period to make every form follow the organisation policy.
- Every run is written to the audit log (
retention.deletedper form with the number of responses,retention.policy_changedwhen the policy changes). - Command line:
php artisan snagbane:retention --dry-runshows what would be deleted; without--dry-runit deletes.
Other tools for data-protection requests: find a person’s responses in Results by searching their name or email, and open or delete each one; export responses as CSV or Excel; the audit log records exports and deletions. Because SnagBane runs on your server, your organisation is the controller of the data in it — SnagBane (the vendor) never receives it.
Payments in forms (Stripe, PayPal)
Business and Enterprise. Settings → Payments: paste a Stripe secret or restricted key (Stripe Dashboard → API keys; a restricted key needs write access to Checkout Sessions and read access to PaymentIntents) and/or a PayPal REST app's client ID and secret (PayPal Developer Dashboard → Apps & Credentials; choose Live or Sandbox). Test the connection checks them.
In a form: Settings → Payment — provider, currency, and a fixed price or a question that holds the total (amount, number, slider or calculation; nothing is charged when it is empty or zero). The amount is always worked out again on the server. After sending, the respondent pays on Stripe Checkout or PayPal; the response is saved right away but integrations, webhooks, approval and emails wait until the payment is confirmed. SnagBane confirms it by asking Stripe or PayPal when the respondent comes back and every 5 minutes for two days — no webhook needs to reach your server. Results show Awaiting payment, Paid or Not paid; an expired payment can be paid again from its link for 7 days. Refunds are made in Stripe or PayPal.
Stripe accounts with Managed Payments (Stripe as merchant of record, meant for digital products only): form payments such as fees, tickets or orders aren't digital products, so SnagBane opens their payment pages with Managed Payments turned off for that payment (from 2.55.1). Your other Stripe sales keep their settings.
Provisioning with SCIM (Microsoft Entra ID, Okta)
Enterprise plan. Settings → Security → SCIM provisioning: switch on, create a secret token (shown once) and copy the address https://<your SnagBane>/api/scim/v2. People assigned to the app in your identity provider get a SnagBane account (role New people start as, Respondent by default — roles are never changed by provisioning), name, job title, department and manager are kept up to date, and people you unassign are switched off (signed out everywhere; their forms and answers stay). Groups pushed from the identity provider appear under Groups with the source SCIM and can be used for workspace access, approvers and roles. A plan's number of people also applies here, and the last active owner can never be switched off by provisioning.
- Microsoft Entra ID: Enterprise apps → New application → Create your own application (non-gallery) → Provisioning → New configuration: Tenant URL = the address, Secret Token = the token → Test Connection → Create; then assign users and groups and start provisioning.
- Okta: your app integration → General → Provisioning: SCIM → Provisioning tab → Integration: SCIM connector base URL = the address, unique identifier
userName, Authentication Mode HTTP Header with the token; choose Push New Users, Push Profile Updates and Push Groups; under To App enable Create, Update and Deactivate Users.
Supported: Users and Groups (create, read, filter with eq/and, paging with startIndex/count, PUT, PATCH with or without a path, delete), /ServiceProviderConfig, /ResourceTypes, /Schemas.
IP allowlist and block list
Enterprise plan. Settings → Security → IP allowlist and block list: SnagBane then answers only requests from the listed addresses and ranges (IPv4/IPv6, CIDR). By default this covers the signed-in app and sign-in; you can also include public forms, portals and approval links, and the REST API and SCIM (cloud services such as Power Automate, Zapier and Entra provisioning call from their own addresses). You can't save a list that leaves out your own address. Block these addresses refuses the listed addresses everywhere — the app, public forms, portals, approval links and the API — even when they are also in the allowlist (your own address can't be blocked). Locked out anyway? On the server run php artisan snagbane:ip-allowlist off (turns both lists off). Behind a reverse proxy, set TRUSTED_PROXIES so SnagBane sees the visitor's real address.
Send logs to a SIEM (Microsoft Sentinel, Elastic, Splunk…)
SnagBane writes everything a security team usually wants as JSON lines (one flat JSON object per line, UTF-8, appended, never rewritten) — the format the log agents of Microsoft, Elastic, Splunk and most other SIEMs read directly. Nothing is sent to anyone unless you set it up.
Straight from SnagBane (Enterprise plan, since 2.47.0): Settings → Security → Send the audit log to your SIEM sends every audit entry within a minute, without an agent — to Microsoft Sentinel (Azure Monitor Logs Ingestion API: logs ingestion endpoint, data collection rule immutable ID, stream name, and an app registration with the Monitoring Metrics Publisher role on the rule; rows carry TimeGenerated plus the fields below), Splunk (HTTP Event Collector: https://host:8088 and a HEC token; optional index and sourcetype), Elastic (bulk create into a data stream, default logs-snagbane.audit-default, with an encoded API key; each document gets @timestamp) or any HTTPS endpoint (POST {"events":[…]} signed with X-SnagBane-Timestamp and X-SnagBane-Signature: sha256=HMAC(secret, timestamp + "." + body)). It starts with entries written after you switch it on; if the SIEM can't be reached, the next minute resumes where it stopped. Send test sends one entry with the action siem.test. The JSON-lines file below keeps working next to it.
| What | Where | Contents |
|---|---|---|
| Audit log | storage/logs/audit/audit-YYYY-MM-DD.log (one file per UTC day) | Who did what: sign-ins and failed sign-ins (auth.login, auth.failed, auth.password_blocked, auth.recovery_code_used; password resets: auth.reset_requested, auth.reset_unknown (address masked), auth.reset_refused, auth.reset_throttled, auth.reset_invalid, auth.reset_completed), users and roles, API tokens, settings, integrations and webhooks, domains, forms and portals, response deletions and exports, updates, licence, database password changes, audit exports; since 2.61.0 also changed approval steps (form.workflow_changed, with the steps added and removed), who may open a form or portal (form.access_changed, portal.access_changed), refused self-approvals (approval.self_approval_refused, also when an approval is handed to the requester) and where a new form came from (form.created with source: template, paste or import). |
| Form events | storage/logs/forms/<form>.log — the Local log file integration on a form (Connect apps) | One line per submission, approval and the other events you pick. |
| Application errors | storage/logs/laravel-*.log; in Docker also the container output (docker compose logs) | Errors and warnings of the app itself. |
Audit record fields (names are stable; new fields may be added, existing ones are never renamed):
| Field | Type | Meaning |
|---|---|---|
time | string, ISO 8601 UTC | When it happened, e.g. 2026-10-01T08:15:02.113Z |
app / site | string | Always snagbane / the host name of the site address (to tell installations apart) |
id | number | Audit entry number (unique per installation, increasing) |
action | string | What happened, e.g. user.updated |
user_id, user_email, user_name | number / string, or null | Who did it (null for the system, e.g. scheduled jobs or failed sign-ins) |
subject_type, subject_id | string or null | What it was done to, e.g. Form / 42 |
ip | string or null | Client address as SnagBane saw it (behind a reverse proxy on a public address, set TRUSTED_PROXIES in .env so this is the real client; private networks are trusted by default) |
meta | string (JSON) | Details as a JSON text, e.g. {"to":"editor"} — kept as text so the line stays flat |
category | string | security (sign-ins, accounts and roles, access, keys and tokens, directory and security settings, refused self-approvals, exports), activity (forms, approval steps, portals, workspaces, responses) or system (updates, the server) — the tabs in Settings → Logs. Since 2.61.0, always the last field. To keep it in a Sentinel table made from the steps below, add a category column (string) to the table and to the rule's stream. |
Example line:
{"time":"2026-10-01T08:15:02.113Z","app":"snagbane","site":"forms.contoso.com","id":5120,"action":"user.updated","user_id":1,"user_email":"admin@contoso.com","user_name":"Ana Admin","subject_type":"User","subject_id":"17","ip":"203.0.113.10","meta":"{\"role\":\"editor\"}","category":"security"}
- Keeping files: day files older than 30 days are removed every night. Change it with
SNAGBANE_AUDIT_DAYS=90in.env;SNAGBANE_AUDIT_FILE=falseturns the files off (the audit log in the database stays). Keep at least a few days so your agent always has time to read a file. - Find and export: Settings → Logs has one tab per kind of log — Security, Activity, Integration runs (every run of every form, also integrations for all forms), Webhook deliveries, API requests (every call to the REST API, SCIM and the Power Automate connector: method, address, route, status, time, person and API token), Performance (requests that took 1 second or longer, slowest first, with the number of database queries), System and All events. API requests and Performance keep addresses without their query string and never store request bodies; rows older than 30 days are removed every night. Each works like a spreadsheet — drag the edge of a column title to make it wider or narrower (double-click the edge to reset; remembered in this browser), and — click a column title to sort and the funnel next to it to filter by values (for example one person or a few actions) or by a condition (date range, contains, is empty); Webhook deliveries filter the same way (webhook, event, delivered/failed, status, time). Export → pick dates (UTC) → CSV or JSON lines exports just the rows that match the filters and search — the same fields, for an auditor or a one-off import.
- Docker: the files are inside the
storagevolume. For an agent on the host, put the audit folder on the host with adocker-compose.override.ymlnext todocker-compose.yml, thendocker compose up -d. All three SnagBane containers write audit entries, so mount it in each:
The agent then readsservices: app: volumes: ["./audit-logs:/var/www/html/storage/logs/audit"] worker: volumes: ["./audit-logs:/var/www/html/storage/logs/audit"] scheduler: volumes: ["./audit-logs:/var/www/html/storage/logs/audit"]./audit-logs/audit-*.login the SnagBane folder on the host.
Microsoft Sentinel (Azure Monitor Agent)
Microsoft Sentinel works on a Log Analytics workspace. The Azure Monitor Agent reads the audit files into a custom table of that workspace with a Custom JSON Logs data collection rule (DCR). The server must be an Azure virtual machine or an Azure Arc-enabled server (for servers outside Azure); you need at least contributor rights on the workspace and permission to create DCRs. Steps from Microsoft's documentation Collect JSON file from virtual machine with Azure Monitor and Collect guest log data from virtual machines:
- Create the table first — Microsoft states the table must exist before the DCR and must be created with a PowerShell script like this one (Azure PowerShell,
Invoke-AzRestMethod). Replace the{…}parts:$tableParams = @' { "properties": { "schema": { "name": "SnagBaneAudit_CL", "columns": [ { "name": "TimeGenerated", "type": "dateTime" }, { "name": "Computer", "type": "string" }, { "name": "FilePath", "type": "string" }, { "name": "app", "type": "string" }, { "name": "site", "type": "string" }, { "name": "id", "type": "int" }, { "name": "action", "type": "string" }, { "name": "user_id", "type": "int" }, { "name": "user_email", "type": "string" }, { "name": "user_name", "type": "string" }, { "name": "subject_type", "type": "string" }, { "name": "subject_id", "type": "string" }, { "name": "ip", "type": "string" }, { "name": "meta", "type": "string" }, { "name": "category", "type": "string" } ] } } } '@ Invoke-AzRestMethod -Path "/subscriptions/{subscription}/resourcegroups/{resourcegroup}/providers/microsoft.operationalinsights/workspaces/{WorkspaceName}/tables/SnagBaneAudit_CL?api-version=2021-12-01-preview" -Method PUT -payload $tableParams - In the Azure portal: Monitor → Data Collection Rules → Create. On Basics give a Rule Name, Subscription, Resource group and a Region that is the same as the workspace's. If the portal asks for a Data Collection Endpoint for this type of data (Help me choose next to Type of telemetry tells you), select or create one.
- Resources → Add resources → the SnagBane server. The portal installs the Azure Monitor Agent when needed.
- Collect and deliver → Add new dataflow → Data source type: Custom JSON Logs:
- File pattern: the audit folder with a wildcard in the file name, e.g.
/var/www/snagbane/storage/logs/audit/audit-*.log(Docker with the mount above:/opt/snagbane/audit-logs/audit-*.log; Windows:C:\snagbane\storage\logs\audit\audit-*.log). Wildcards are allowed only in the file name and the first folder above it. - Table name:
SnagBaneAudit_CL. - Transform: use SnagBane's own time as the record time:
source | extend TimeGenerated = todatetime(time) | project-away time - JSON Schema: the fields from the table above.
- Destination: Azure Monitor Logs → your Sentinel workspace.
- File pattern: the audit folder with a wildcard in the file name, e.g.
- Create the rule. Data can take up to 5 minutes to arrive. Check it in the workspace (Logs):
SnagBaneAudit_CL | take 10. From there, write Sentinel analytics rules on it, e.g. manyauth.failedfor oneip, oruser.updatedwheremetacontains"owner".
Microsoft's notes that apply: files must be UTF-8 JSON lines and appended (SnagBane does both); don't rename or copy files into the watched folder; keep the number of watched folders small.
Elastic (Filebeat or Elastic Agent)
Use a filestream input with the ndjson parser — from Elastic's filestream input reference. In filebeat.yml:
filebeat.inputs:
- type: filestream
id: snagbane-audit
paths:
- /var/www/snagbane/storage/logs/audit/audit-*.log
parsers:
- ndjson:
target: ""
add_error_key: true
- Each
filestreaminput needs its own uniqueid; don't change it later (Elastic warns this causes duplicates). target: ""puts SnagBane's fields at the top level of the event;add_error_keymarks lines that aren't valid JSON.- By default filestream starts reading a file only when it is larger than 1024 bytes, so the first entries of a new day can appear a little later on a quiet installation.
Splunk (universal forwarder)
Install the Splunk universal forwarder on the SnagBane server (or, with Docker, on the host that has the ./audit-logs folder from above) and connect it to your Splunk indexers or Splunk Cloud Platform as Splunk describes for your environment. Then:
- Create an index for SnagBane in Splunk (for example
snagbane), or use an existing one. - On the forwarder, open
$SPLUNK_HOME/etc/system/local/inputs.conf(create it if it doesn't exist) and add a monitor stanza for the audit folder. Use the real path of your installation:[monitor:///var/www/snagbane/storage/logs/audit] index = snagbane sourcetype = _json disabled = 0_jsonis Splunk's source type for JSON-formatted events; each line is one event. Add a second stanza forstorage/logs/formsif you also want form events. - Restart the forwarder (
$SPLUNK_HOME/bin/splunk restart) and check the input with$SPLUNK_HOME/bin/splunk list monitor. - In Splunk, search
index=snagbane action=auth.failedto see failed sign-ins.
Splunk also accepts the input from the command line on the forwarder (splunk add monitor <path>), which writes the same inputs.conf for you. Checked against Splunk's documentation “Monitor files and directories with inputs.conf” and “Configure the universal forwarder using configuration files”.
Other SIEMs
Any agent that monitors a file and parses JSON lines works: point it at storage/logs/audit/audit-*.log (and, if you like, storage/logs/forms/*.log) and treat each line as one JSON event whose time is the time field (ISO 8601, UTC).
To push form events straight to an HTTP collector instead of a file, use the HTTP request integration (your own URL, headers such as Authorization — stored encrypted — and a JSON body with merge tags) or a signed webhook.
FAQ & troubleshooting
"AADSTS50011: redirect URI mismatch"
Your site URL changed. Re-run setup, or add https://your-domain/auth/microsoft/callback to the app registration's redirect URIs.
The health check fails right after connecting
A new client secret can take up to a minute to become active; SnagBane waits for it during setup. If it still fails, wait a minute and click Run health check again.
The health check says permissions aren't granted
Run health check compares the features you ticked with the application permissions Microsoft has actually granted to SnagBane's app, and names the missing ones (e.g. after someone removed admin consent in Entra ID). Click Re-run setup / change permissions and sign in once as a Global Administrator; it updates the same app registration.
Device-code sign-in is blocked
Your Conditional Access policy blocks the device-code flow. Use the PowerShell or Manual tab instead.
Uploads fail
Raise upload_max_filesize and post_max_size in PHP, and the Max upload size in Branding & general.
Emails aren't sent
Configure Settings → Email and use Send test email. For Microsoft 365 SMTP, SMTP AUTH must be enabled on the mailbox. You can use the Graph option instead.
Credits & licences
SnagBane is proprietary software covered by the licence agreement: the Free plan is free to use inside your organisation, Business and Enterprise need a subscription. The server code is included in readable form so you can review it; it may not be redistributed, resold or offered to others as a service. The SnagBane name and logo are trademarks.
Laravel validation translations from Laravel-Lang (MIT). Laravel (MIT), Vue.js (MIT), Vue Router (MIT), Tailwind CSS (MIT), Lucide icons (ISC), SortableJS (MIT), node-qrcode (MIT), Microsoft Teams JavaScript client library (MIT), Inter font (SIL OFL 1.1). Microsoft, Microsoft 365, Entra ID, Teams and Power Automate are trademarks of Microsoft Corporation. SnagBane is not affiliated with Microsoft.