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

ComponentMinimum
PHP8.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)
DatabaseMySQL 8 / MariaDB 10.6+ (recommended), PostgreSQL 14+, or SQLite 3.35+
Web serverApache (with mod_rewrite) or Nginx, with HTTPS (required by Microsoft for sign-in redirect URIs other than localhost)
Outbound HTTPSTo 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

  1. 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 in DB_PASSWORD in .env, or run php artisan snagbane:db-password, which changes both.
  2. Upload snagbane.zip with File Manager and extract it, for example into /home/you/snagbane.
  3. Point your (sub)domain's document root to /home/you/snagbane/public. If your host doesn't allow that, move the contents of public/ into public_html/ and fix the two paths in public_html/index.php to point at ../snagbane/.
  4. Open https://your-domain/install and follow the three steps: requirements, database, admin account.
  5. 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.
  6. 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):

  1. Install Laragon Full, which includes PHP 8.3, MySQL and Apache.
  2. Copy the SnagBane folder into C:\laragon\www\snagbane.
  3. Start Laragon. It serves the site at http://snagbane.test.
  4. 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.

Microsoft allows 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.

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:

  1. downloads the new version and checks its digital signature (a package that isn't signed by SnagBane is refused);
  2. saves a backup of the current version (Settings → System & updates → Restore previous version undoes the update);
  3. 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/docs and database/migrations, seeders, factories), so leftovers can't break it; your own files elsewhere (for example in public/) stay. Your .env, storage/, uploads and data are never touched.

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:

  1. 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.
  2. 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.
  3. 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

FeaturePermissionTypeWhy
Sign in with Microsoftopenid, profile, email, offline_access, User.ReadDelegatedSign users in and read their own profile
Person picker & prefillUser.Read.AllApplicationSearch the directory and read job title, department, manager and profile photo
GroupsGroupMember.Read.AllApplication"Specific groups" form access and mapping the admin group
MailMail.SendApplicationSend notifications from a mailbox. Restrict this with an application access policy.
SharePoint / ExcelSites.ReadWrite.All, Sites.Manage.AllApplicationSave responses to SharePoint lists (Manage: create lists and columns) and files
SharePoint / ExcelFiles.ReadWrite.AllDelegatedExcel rows, written as the connected Excel account (see below)
Create and switch off accountsUser.ReadWrite.All, User.RevokeSessions.All, GroupMember.ReadWrite.All, LicenseAssignment.ReadWrite.All, LicenseAssignment.Read.AllApplicationStrong 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 calendarsCalendars.ReadWriteApplicationPut 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.

  1. 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.
  2. 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)
SetupEnable 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 asOne 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.
GmailSends only from the connected account.From the mailbox you choose.
CalendarShared 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 / SheetsFolders 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 stopsIf 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:

  1. Applications and Resources → Applications → Create App Integration (Classic experience) → sign-in method OIDC – OpenID Connect, application type Web Application → Next.
  2. 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.
  3. 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.
  4. 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. default for a custom one.
  5. 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.
  6. 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).
  7. 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.

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 valueWhere it goes
Entity ID https://forms.yourcompany.com/auth/saml/metadataIdentifier / Audience / Relying party identifier
Reply URL https://forms.yourcompany.com/auth/saml/acsAssertion 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.

Microsoft Entra ID

  1. 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.
  2. 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.
  3. In SAML Certificates, copy the App Federation Metadata Url and import it in SnagBane.
  4. 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 sAMAccountName as the source attribute.
  5. Users and groups: assign who may sign in.

AD FS

  1. 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.
  2. 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.
  3. Add Rule → Transform an Incoming Claim: incoming E-Mail Address → outgoing Name ID (format Email).
  4. In SnagBane, import AD FS’s metadata: https://fs.yourcompany.com/FederationMetadata/2007-06/FederationMetadata.xml.

Okta (SAML instead of OpenID Connect)

  1. Okta Admin Console → Applications → Create App Integration → SAML 2.0.
  2. Single sign-on URL = SnagBane’s reply URL, Audience URI (SP Entity ID) = SnagBane’s entity ID, Name ID format EmailAddress.
  3. Optional: Attribute Statements (e.g. displayName) and a Group Attribute Statement named groups with a filter such as Matches regex .*.
  4. 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.

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).

Getting around

The menu on the left has the same places for everyone (you only see what your role allows):

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.

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.

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.

Before you connect a tenant, SnagBane uses a built-in demo directory (Contoso users), so you can try everything first.

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.

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:

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

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

  1. Someone submits the form → the response gets status pending and the first stage starts (stages whose condition doesn't match are skipped).
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.
  8. 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?

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.

  1. Open SnagBane in the browser on the phone and sign in. Under Approvals you see Approvals on your phone.
  2. 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.
  3. Notifications: tap Turn on notifications and allow them. Each device is turned on separately; turn it off with the same button.

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).

Upgrading from 2.3 or earlier: existing forms, portals and integrations move into a workspace called “General”, and every active administrator becomes an Owner, so nobody loses access. Editors keep seeing their own forms and those shared with them; add people to workspaces when you're ready.

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:

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:

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:

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.

FreeBusinessEnterprise
People (active accounts, any role)up to 25up to 50, 100, 250, 500 or unlimitedup 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-inSnagBane accounts, two-step verification+ Microsoft, Google, Okta and SAML 2.0+ sign-in limited to chosen directory groups
Person picker & prefillfrom SnagBane usersfrom Entra ID / Google Workspace / Okta, fast local directory copy✓
Groups from Entra ID / Google / Okta / SAML—✓✓
EmailSMTP, SendGrid, Mailgun, Postmark, Brevo, Resend, Amazon SES+ Microsoft 365 mailbox, Gmail✓
IntegrationsMake, 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 licenceany number12 (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).

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.

  1. Permission: Settings → Microsoft 365 → Re-run setup / change permissions, tick Notify approvers in Microsoft Teams (application permissions TeamsActivity.Send and AppCatalog.Read.All — the second one finds the app's id in your org catalog) and sign in once as a Global Administrator.
  2. Teams sign-in must be on (card Microsoft Teams sign-in on the same page): the app signs people in with it.
  3. 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.
  4. 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:

  1. 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.
  2. Upload it: a Teams administrator opens admin.teams.microsoft.com → Teams apps → Manage apps → Upload new app and selects the zip.
  3. Make it available: check that the app is Allowed. To pin it for everyone: Teams apps → Setup policies → Global → Pinned apps → Add apps.
  4. 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.
  5. 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)

  1. Portal editor → Microsoft Teams → Website tab → Copy the tab link (it ends in ?teams=1).
  2. In Teams, open the channel → + (Add a tab) → Website → give the tab a name, paste the link → Save.
  3. 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

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).

Save & resume, editing and receipts

Access control

ModeWho can respond
Anyone with the linkPublic, no sign-in
My organisationAnyone who signs in with your Microsoft 365 tenant
Specific people & groupsSelected 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):

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.

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.

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.

CategoryConnectors
Chat & notificationsMicrosoft Teams, Slack, Telegram, Discord, Google Chat, Twilio SMS / WhatsApp
Email & marketingSend 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 & databasesGoogle Sheets, Excel tables (auto-creates the table and columns), SharePoint lists (auto-creates the list and columns), Airtable, Notion
Files & storageDropbox, Google Drive, SharePoint / OneDrive, Amazon S3 / Cloudflare R2 / Wasabi / MinIO
CRM & customDynamics 365 and Salesforce (Business), HubSpot, Custom HTTP request (any REST API)
Projects & ticketsJira (Cloud and Data Center), ClickUp, Asana, monday.com, Trello
CalendarOutlook calendar, Google Calendar
Accounts (onboarding & offboarding)Microsoft 365 accounts, Google Workspace accounts, Okta accounts, SnagBane accounts (Enterprise plan, see Account integrations)
Local serverLocal folder export (CSV + JSON lines + per-response folders with files), Local log file (JSON lines for SIEM)
AutomationPower 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.

KindFunctions
Textconcat, 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'))
Conditionsif(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
Numbersadd, sub, mul, div, formatNumber(number, decimals)
DatesutcNow('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.

Projects, tickets and calendars

Enterprise. These connectors turn a response or an approved request into work in the tools your teams already use.

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
  1. In ActiveCampaign, click Settings (gear icon) → Developer.
  2. Copy the API URL and the API key into the fields below.
  3. Optional: the numeric ID of the list to subscribe contacts to.

Official guide: help.activecampaign.com

Adobe Acrobat Sign (Business)
  1. 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.
  2. Select Configure OAuth for Application: enter https://your-snagbane-address/integrations/adobe-sign/callback as the redirect URI and enable agreement_read, agreement_write and agreement_send (modifier self, or account to send as any user), then save.
  3. 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.
  4. 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
  1. Open airtable.com/create/tokens and click Create new token. Name it e.g. SnagBane.
  2. Scopes: click Add a scope and choose data.records:write.
  3. 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.
  4. 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.
  5. 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.
  6. Click Send test: a test record appears in the table.

Official guide: airtable.com

Amazon S3 / R2 / Wasabi / MinIO (Business)
  1. AWS: create the bucket, then IAM → Users → Create user (no console access needed).
  2. Attach a policy that allows s3:PutObject on arn:aws:s3:::your-bucket/*.
  3. Open the user → Security credentials → Create access key (“Application running outside AWS”) and copy both keys.
  4. Leave Endpoint empty and enter the bucket’s region.
  5. 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.
  6. 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.
  7. MinIO, Wasabi and others: use their S3 endpoint and access keys.

Official guide: docs.aws.amazon.com

Asana (Business)
  1. Open the Asana developer console (app.asana.com/0/my-apps) and create a personal access token. Copy it.
  2. Open the project the tasks should go to and copy its address from the browser.
  3. Optional: map answers to the project’s custom fields by their name.
  4. Save and click Send test.

Official guide: developers.asana.com

Brevo contacts
  1. In Brevo, open the account menu → Settings → SMTP & API → API Keys & MCP.
  2. Click Generate a new API key, name it, choose an expiry and copy it (it is shown only once).
  3. Optional: the IDs of the lists to add contacts to (Contacts → Lists), separated by commas.

Official guide: help.brevo.com

ClickUp (Business)
  1. 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_…).
  2. 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.
  3. Optional: map answers to the list’s custom fields by their name.
  4. Save and click Send test.

Official guide: developer.clickup.com

Custom HTTP request
  1. Take the URL and method from the service’s API documentation.
  2. Add headers such as Authorization: Bearer … (stored encrypted).
  3. Write the JSON body with merge tags; values are escaped automatically.
  4. Save and click Send test to see the reply.
Discord
  1. In Discord, open Server Settings → Integrations (you need the Manage Webhooks permission).
  2. Click Create Webhook (or Webhooks → New Webhook), give it a name and choose the channel.
  3. Click Copy Webhook URL and paste it below.

Official guide: support.discord.com

DocuSign (Business)
  1. 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.
  2. 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/consent and select SAVE.
  3. 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.
  4. 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
  1. Open dropbox.com/developers/apps → Create app → Scoped access → App folder (or Full Dropbox).
  2. On the Permissions tab tick files.content.write and files.content.read, then click Submit.
  3. Copy the App key and App secret from the Settings tab into the fields below and save.
  4. 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.
  5. 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)
  1. Sign in at sign.dropbox.com, select your avatar → Settings → API, and select Reveal key (or Create key) next to API key.
  2. 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.
  3. 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)
  1. Copy your environment URL: Power Platform admin center → Manage → Environments → your environment → Environment URL (e.g. https://contoso.crm4.dynamics.com).
  2. 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.
  3. 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.
  4. Pick the table and map answers to its columns by their logical names (e.g. firstname, lastname, emailaddress1, subject).
  5. 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)
  1. 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).
  2. Open the workbook in Excel or SharePoint → Share → Copy link, and paste it below. The connected account must be able to edit it.
  3. 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.
  4. 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.
  5. Save and click Send test.
Google Calendar (Business)
  1. In Settings → Google Workspace, tick “Add approved requests to Google Calendar” and save.
  2. 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.
  3. Choose whose calendar. For a shared calendar, enter a mailbox that can edit it and the calendar’s ID (Calendar settings → Integrate calendar).
  4. 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)
  1. 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.
  2. 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.
  3. Optional: limit which mailboxes SnagBane may send from with Exchange Online RBAC for Applications.
  4. People who aren’t administrators can only use mailboxes listed under Settings → Security.

Official guide: learn.microsoft.com

Gmail (Business)
  1. In Settings → Google Workspace, tick “Send email from a Workspace mailbox (Gmail API)” and save.
  2. 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.
  3. 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”.
  4. With “Connect with Google”, email goes out only from the connected account and its “Send mail as” addresses.
  5. People who aren’t administrators can only use mailboxes listed under Settings → Security.

Official guide: developers.google.com

Google Workspace accounts (Enterprise plan)
  1. 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.
  2. Use a new-starter or leaver form with an approval step. Choose what this integration does and insert the form’s questions.
  3. 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.
  4. 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)
  1. Open the space in Google Chat in a browser (webhooks can’t be added in the mobile app).
  2. Click the arrow next to the space name → Apps & integrations → Add webhooks.
  3. Enter a name and click Save, then click More (⋮) → Copy link and paste it below.
  4. Option missing? Your Google Workspace admin has to allow incoming webhooks.

Official guide: developers.google.com

Google Drive (Business)
  1. Set up the service account once in Settings → Google Workspace, with the Google Drive API enabled.
  2. Service accounts have no storage of their own: open a Shared Drive → Manage members and add the service account email as Content manager.
  3. Open the folder in that Shared Drive and paste its link below.
  4. 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)
  1. Set up the service account once in Settings → Google Workspace, with the Google Sheets API enabled.
  2. Open the spreadsheet → Share and add the service account email as Editor.
  3. Paste the spreadsheet link below. The tab is added if it doesn’t exist.

Official guide: developers.google.com

HubSpot
  1. In HubSpot, open Development → Keys → Service keys (or Settings → Integrations → Service keys) and create a key. You need Developer tools access.
  2. Name it and add the scope crm.objects.contacts.write.
  3. Copy the key and paste it below. A token from an existing legacy private app works too.
  4. Map answers to HubSpot contact properties below.

Official guide: developers.hubspot.com

Jira (Business)
  1. 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.
  2. Jira Data Center or Server (8.14 or later): your avatar → Profile → Personal access tokens → Create token. Leave the email empty.
  3. 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.
  4. Optional: map answers to Jira fields by their name or ID (Priority, Due date, Assignee, customfield_10010…).
  5. Save and click Send test.

Official guide: developer.atlassian.com

Kit (ConvertKit)
  1. In Kit, open Settings → Developer and add a new API key (v4).
  2. Copy the key (it is shown only once) and paste it below.
  3. 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
  1. Files are written on the server below the export root shown under Sub-folder.
  2. With Docker, mount that folder as a volume or network share to reach the files.
  3. Choose what to write: responses.csv, responses.jsonl and/or a folder per response.
Local log file
  1. Each event is appended as one JSON line to storage/logs/forms/<form>.log.
  2. Point your log shipper (Filebeat, Fluent Bit, Vector, Splunk forwarder…) at storage/logs/forms/*.log.
  3. Turn off the Include answers option for privacy-sensitive forms.
Mailchimp
  1. In Mailchimp, click your profile icon → Profile → Extras → API keys.
  2. 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.
  3. Audience ID: click Audience, pick the audience if you have several, then More options → Audience settings, and copy the Audience ID.
  4. 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.
  5. 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}}.
  6. Click Send test: it checks the key, the audience and the merge tags without adding anyone.

Official guide: mailchimp.com

MailerLite
  1. In MailerLite, open Integrations → MailerLite API → Use.
  2. Click Generate new token, name it and copy it right away (it is shown only once).
  3. 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)
  1. In Settings → Microsoft 365, click “Re-run setup / change permissions”, tick “Create and switch off accounts” and sign in once as a Global Administrator.
  2. Use a new-starter or leaver form with an approval step. Choose what this integration does and insert the form’s questions.
  3. 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.
  4. 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)
  1. In Teams, click ⋯ next to the channel or chat and choose Workflows.
  2. Pick “Send webhook alerts to a channel” (or “Send webhook alerts to a chat”).
  3. Check the team and channel, click Save and copy the webhook link.
  4. Paste it below, save, then click Send test.

Official guide: support.microsoft.com

monday.com (Business)
  1. In monday.com, click your profile picture → Developers. In the Developer Center click API token → Show and copy it.
  2. Open the board the items should go to and copy its address from the browser.
  3. Optional: map answers to the board’s columns by their title (text, numbers, status, dropdown, date, email, link, checkbox).
  4. Save and click Send test.

Official guide: developer.monday.com

Notion
  1. In Notion, open Settings → Connections and turn on Developer Mode (workspace owners only).
  2. In the developer section of the sidebar, click + New connection, name it (e.g. SnagBane) and pick the workspace.
  3. Back in Settings → Connections, click ••• next to the connection, copy its token (it starts with ntn_) and paste it under Integration secret.
  4. 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.
  5. Copy the database link (••• → Copy link, or the browser’s address bar) and paste it under Database URL or ID.
  6. 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.
  7. Click Send test: a new page appears in the database.

Official guide: developers.notion.com

Okta accounts (Enterprise plan)
  1. 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.
  2. 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.
  3. Use a new-starter or leaver form with an approval step. Choose what this integration does and insert the form’s questions.
  4. 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)
  1. 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.
  2. Optional: limit which mailboxes SnagBane may write to with Exchange Online RBAC for Applications.
  3. Choose whose calendar, and insert the form’s date questions as start and end.
  4. Keep “Run on” at Approved, so only approved requests reach the calendar. Save and click Send test.

Official guide: learn.microsoft.com

Salesforce (Business)
  1. In Salesforce Setup search for External Client App Manager → New External Client App (Distribution State: Local).
  2. 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.
  3. On the app’s Policies tab → Edit → OAuth Policies: set Run As to an integration user that may create the records. Save.
  4. 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.
  5. 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
  1. Email is sent through the service set up in Settings → Email.
  2. Enter recipients separated by commas; a merge tag such as {{email}} sends to an address from the form.
  3. Add conditions below to route by answer, e.g. a different team per department.
SnagBane accounts (Enterprise plan)
  1. Use a form with a person or email question for the person, and an approval step.
  2. 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.
  3. 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)
  1. In Settings → Microsoft 365, turn on “Save responses to SharePoint / Excel” and grant admin consent.
  2. Enter the site address; files are saved in the site’s Documents library.
  3. Choose a folder; each response gets its own sub-folder.
  4. Save and click Send test to check the permissions.
SharePoint list (Business)
  1. In Settings → Microsoft 365, turn on “Save responses to SharePoint / Excel” and grant admin consent.
  2. Enter the site address, e.g. https://contoso.sharepoint.com/sites/HR.
  3. Choose a list name. With automatic creation on, the list and its columns are created for you.
  4. Save and click Send test to check the permissions.
Slack (Business)
  1. Go to api.slack.com/apps and create an app (From scratch) in your workspace.
  2. Open Incoming Webhooks and turn on Activate Incoming Webhooks.
  3. Click Add New Webhook to Workspace, pick the channel and click Authorize.
  4. Copy the webhook URL (https://hooks.slack.com/services/…) and paste it below.

Official guide: docs.slack.dev

Telegram
  1. In Telegram, message @BotFather and send /newbot.
  2. Choose a name and a username ending in “bot”, then copy the token BotFather sends you.
  3. Add the bot to your group, or as an administrator of your channel.
  4. 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)
  1. 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).
  2. In the app: Authorization → Trello Auth. Copy the API key (not the Secret).
  3. 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).
  4. Paste the board’s address (trello.com/b/…) and the name of the list the cards should go to.
  5. Save and click Send test.

Official guide: developer.atlassian.com

Twilio SMS / WhatsApp
  1. Sign in to the Twilio Console (console.twilio.com). The Account SID and Auth token are under Account Info.
  2. 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.
  3. New Twilio trial accounts only send from the Console’s “Try out” pages: to send from SnagBane, upgrade the account (pay as you go).
  4. To try WhatsApp: Console → Messaging → Try WhatsApp, and join the sandbox from your phone first.
  5. 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.

  1. 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.
  2. 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.
  3. 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)

  1. In SnagBane, open Automations & API and copy the connector address (https://your-domain/integrations/power-automate/snagbane.swagger.json).
  2. 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.
  3. 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 — Bearer in front is optional. Give the connection a name you'll recognise.
  4. 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.

ServiceIn the serviceIn SnagBane
ZapierCreate 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.
MakeCreate 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.
n8nAdd 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 & pathDescription
GET /meThe token's user
GET /formsList 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

Cloudflare

AWS (AWS WAF on CloudFront, Application Load Balancer, API Gateway)

Azure (Front Door, Application Gateway, App Service)

Other firewalls and reverse proxies (F5, Fortinet, Imperva, Akamai, nginx, IIS…)

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-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.

MethodWhat you needGood for
Authenticator appMicrosoft 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 computerWindows Hello (PIN, fingerprint or face), Touch ID on a MacOne click at every sign-in on your own computer
Passkey on your phoneGoogle Password Manager on Android, iCloud Keychain on iPhone, or a password manager that stores passkeysSigning in on any computer with your phone nearby
Security keyA 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)

  1. Open Your profile and click Set up next to Authenticator app.
  2. 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.
  3. 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)

  1. In Your profile → Security keys and passkeys, type a name you will recognise (“Work laptop”, “YubiKey”, “Phone”) and click Add security key or passkey.
  2. 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.
  3. 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

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:

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.

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.

WhatWhereContents
Audit logstorage/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 eventsstorage/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 errorsstorage/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):

FieldTypeMeaning
timestring, ISO 8601 UTCWhen it happened, e.g. 2026-10-01T08:15:02.113Z
app / sitestringAlways snagbane / the host name of the site address (to tell installations apart)
idnumberAudit entry number (unique per installation, increasing)
actionstringWhat happened, e.g. user.updated
user_id, user_email, user_namenumber / string, or nullWho did it (null for the system, e.g. scheduled jobs or failed sign-ins)
subject_type, subject_idstring or nullWhat it was done to, e.g. Form / 42
ipstring or nullClient 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)
metastring (JSON)Details as a JSON text, e.g. {"to":"editor"} — kept as text so the line stays flat
categorystringsecurity (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"}

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:

  1. 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
  2. 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.
  3. Resources → Add resources → the SnagBane server. The portal installs the Azure Monitor Agent when needed.
  4. 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.
  5. 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. many auth.failed for one ip, or user.updated where meta contains "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

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:

  1. Create an index for SnagBane in Splunk (for example snagbane), or use an existing one.
  2. 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
    _json is Splunk's source type for JSON-formatted events; each line is one event. Add a second stanza for storage/logs/forms if you also want form events.
  3. Restart the forwarder ($SPLUNK_HOME/bin/splunk restart) and check the input with $SPLUNK_HOME/bin/splunk list monitor.
  4. In Splunk, search index=snagbane action=auth.failed to 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.