Revup Manual
Revup — User Manual
Review requests on your own server — every customer who rates is shown your review link. Version 4.1.3 [src: app/src/App.php:8].
About this manual
Every statement here was written by reading Revup's own source, and each one carries the file it came from in a bracketed src marker. Paths are relative to the folder this docs directory sits in. If a sentence disagrees with the application, the application is right and this manual has a bug — the markers exist so you can check rather than take our word for it.
Nothing is described that the code does not do. Where something is deliberately limited, that is said plainly rather than left out.
What Revup does
A customer gets a one-tap rating link. Every customer who rates is then shown your public review page, whatever the rating [src: app/src/Revup.php:74]. Customers who rate below your threshold are also offered a private message to you, on the same page and below the review link — never instead of it [src: app/src/Revup.php:60].
The default threshold is 4, so one to three stars are also offered the private message [src: app/src/Database.php:231]. The threshold is a setting, clamped to 1–5; "Do not offer a private message" (1) switches the offer off [src: app/views/settings.php:21].
Why every customer sees the review link
Google's policy for Maps reviews lists "Discourage or prohibit negative reviews, or selectively solicit positive reviews from customers" among prohibited practices (support.google.com/contributionpolicy/answer/7400114, read 2026-09-24). Revup versions before 3.1.4 showed the review link only to ratings at or above the threshold — the practice commonly called review gating. Since 3.1.4 no rating decides whether the link is shown [src: app/src/Revup.php:50].
The dashboard counts who opened your review page from the rating page — the link goes through Revup once, which records it and fires the rating.public webhook — and who wrote to you privately [src: app/controllers/app.php:616].
What this manual will not do is tell you what the law in your jurisdiction requires, or what any other platform's terms say. Read the terms of whichever platform you link to.
Requests and links
Each request carries a token of 40 hexadecimal characters from a cryptographic source, uniquely indexed [src: app/src/Revup.php:99].
A link is single-use, and the rule is enforced in the database rather than in application logic. The update only lands where the rating is still null, and the call reports whether it actually consumed the token [src: app/src/Revup.php:115].
That is the same shape as an oversell guard: two taps on the same link cannot both record a rating, however they race.
The public rating page
It shows a first name and nothing more. The source is explicit that the public rating page never shows more personal information than that [src: app/src/Revup.php:30], and an empty name becomes "there" rather than a blank [src: app/src/Revup.php:34].
The page is rate-limited per address — sixty hits in ten minutes by default — with old rows pruned as it goes [src: app/src/Revup.php:343].
Message templates
Templates substitute {name}, {business} and {link} [src: app/src/Revup.php:20].
Values are inserted raw, deliberately: the text goes into an email or SMS body rather than into HTML, so escaping it would put backslashes and entities in front of your customers. Previews rendered in the browser are escaped by the view instead [src: app/src/Revup.php:17].
If you paste HTML into a template, understand which of those two paths it is taking.
Importing contacts
CSV import is capped at 5,000 rows [src: app/src/Revup.php:242], and every write clips each field to its column width [src: app/src/Revup.php:137].
Users and roles
Three roles [src: app/controllers/v3.php:25]. The source names what stays with the owner, and the reason is a good one: deleting anything is reserved, because feedback and ratings are the record the reports are built from [src: app/controllers/v3.php:23].
| Permission | viewer | member | admin |
|---|---|---|---|
.view permissions | yes | yes | yes |
audit.view | yes | yes | yes |
contact.edit | — | yes | yes |
request.create / request.send | — | yes | yes |
batch.run | — | yes | yes |
feedback.handle | — | yes | yes |
| everything else, including deletion and the private-message threshold | — | — | yes |
⚠️ The audit trail is readable by a viewer
Revup's viewer role is granted *.view — a wildcard rather than a list [src: app/controllers/v3.php:28]. Because the audit permission is named audit.view, the wildcard matches it, so a viewer can read the audit trail. This has been raised against the product.
Backups follow the model
Revup's database backup downloads are permission-gated, requiring the settings permission [src: app/controllers/api.php:223]. A viewer cannot download the database.
The API and agent access
The instructions state the boundary clearly, and it is the right one: an agent cannot change the threshold or the review URL — those stay with the owner [src: app/controllers/mcp.php:63].
create_request writes — it queues a request for a customer [src: app/controllers/mcp.php:63]. Request status is queued, pending, sent or failed [src: app/controllers/mcp.php:62].
An automation can therefore ask more people for reviews, and cannot change the rating page your customers see.
After the rating (3.2.0)
Your review link stays first for everyone who rates. Below it, each of these appears only when it applies and only when you switched it on:
- "May we quote you?" for 4 and 5 stars (on by default; it publishes nothing by itself). Approve comments on Testimonials, switch the widget on there, and paste the two lines into your website. The widget can show the average of every rating received beside the comments.
- The referral link for your top raters (Settings). Their friends land on your page with
?ref=and the customer's code; Testimonials lists visits per customer. - The NPS question (Settings), once per request. The score is on Team performance.
Team performance (3.2.0)
Name who did the job on the customer's record ("served by"), on New requests, in a CSV column called "served by" or "staff", or through the API and MCP (staff). Team performance shows each person's asks, response rate, average rating, five-star count, opened review pages and private messages for 30 days, 90 days, 12 months or all time.
Replying to a private message (3.2.0)
Each message in Private feedback has a reply box: send it by email through your own SMTP (the message is then marked handled and the reply kept with it), or save it as a draft. If you connect your own AI assistant over MCP, its draft_reply tool fills that box; nothing it writes goes out until a person sends it.
Automatic asks (3.2.0)
Automatic asks (admins) creates a trigger: a private address another app posts to. Pick where it comes from (Jobora, Slotly, or any system), the campaign, and when to ask — at once, or in the trigger's own send window, which appears under Send windows with its hour and optional follow-up. Paste the address into the other app's webhook settings and, where that app shows one, its signing secret here. Jobora and Slotly name the customer without their email, so give the trigger that app's address and an API key. Every delivery is listed with what Revup did about it.
What Revup does not do
It does not integrate with Google or Yelp. The rating page links to a review URL you supply; there is no platform API, and Revup neither reads nor answers reviews posted there.
It does not post to social media. Sharing an approved comment is a link you click, or your own automation fed by the testimonial.approved webhook.
It does not read any platform's terms for you. It shows every rater your review link; check the terms of the platform you link to.
It does not suppress a review anybody has already written. The rating page collects a rating through your own link; it has no reach into a public platform.
It does not let an agent move the threshold [src: app/controllers/mcp.php:63].
It does not let a rating be recorded twice [src: app/src/Revup.php:115], and it does not delete feedback for anyone below an administrator [src: app/controllers/v3.php:23].
Looking after your install (from 4.0)
Updates from inside Revup
Updates in the menu opens the Updates page (/updates) [src: app/views/updates.php]. Paste your license key once and press Save key: either key works, the one in your Lemon Squeezy receipt or the one on your order page (it starts with OWX-). Check for updates asks the store for the newest release your license covers; Revup never checks by itself.
When a new version is ready, Back up and update to that version backs up your data and the current version, installs the new one and checks it. If the update is interrupted, or the new version's database step or that check fails, the previous version is put back by itself. Revup is unavailable for about a minute while this runs. The update replaces Revup's files, including any you edited, except .htaccess, .user.ini and robots.txt: an edited copy of those is kept, with the new version's copy beside it as .new to compare and merge. The Health page lists any file that differs from the version installed, so you can see that before you update. A copy of the previous version (data/updates/code-<version>/) and a backup of your data from just before the update stay in data/updates/ [src: app/src/Updater.php]. If Revup shows an error page after an update anyway, that is your way back: copy the files from code-<version>/ over the app's files, then restore the backup on the backup page. Updating from the page needs PHP's sodium and Phar extensions; if one is missing, the Updates and Health pages say so and updates stay by hand. Where Revup is hosted for you, the host updates it and the page says so.
Reaching 4.0 from a 3.x version is the last update done by hand: replace the files as before (config.php and data/ stay, and the database migrates itself on the next page load). From 4.0 on, the Updates page does it.
Starting with sample data
The installer offers Start with sample data: a dental practice's contacts, campaigns and review requests to try things on. You can remove it in one click. [src: app/install/index.php:131]. It is ticked by default; untick it to start empty. The sample is added when you open the first page after the installer.
When you are done with it, press the remove button in the sample banner. It asks "Remove the sample contacts, campaigns and requests? Your own records stay." Only the sample goes: your own records stay, and a sample record that one of your own records uses is kept and named in the report [src: app/src/SampleData.php].
Backups by themselves, and a copy somewhere else
On the Backup & restore page (/restore), Back up once a day by itself is on by default; untick it to stop [src: app/views/restore.php:82]. The first visit at least 24 hours after the last backup starts the next one, after the page has loaded, so no cron job is needed. The newest 14 stay in data/backups/, with credentials left out of them (a restore keeps the live values for those). Back up now takes one straight away.
To keep a copy somewhere else as well, fill in an S3-compatible bucket (Backblaze B2, Wasabi, AWS S3 or your own MinIO): the bucket address (an https endpoint), the region, the bucket name, an optional folder, and the access key ID and secret key. Leave the address empty to keep backups on this server only. The copy goes only to the bucket you set; nothing is sent to Ownware. Once a week Revup reads the newest backup back through the restore's own dry run and keeps the result for the Health page [src: app/src/BackupTargets.php].
On Nginx: keep data/ private
On Apache, the .htaccess that ships with Revup keeps data/ (your backups included), src/, config.php and the other private files off the web. Nginx does not read .htaccess, so add these three blocks to your server block, above the block that passes .php files to PHP (Nginx uses the first one that matches):
location ~ ^/(src|data|bin|tests|controllers|deploy|cron)/ { deny all; }
location ~ ^/views/.+\.php$ { deny all; }
location ~ (^|/)(config\.php|\.demo-mode)$|\.sqlite$ { deny all; }
Then open /config.php in a browser: it should answer 403. Only the template files under views/ are closed, not the folder: the app's own saved-view addresses start with /views/ too, and they must reach it.
A complete server block for a new site (PHP-FPM 8.3; change the name, the folder and the socket to yours):
server {
listen 80;
server_name example.com;
root /var/www/revup/app;
index index.php;
location ~ ^/(src|data|bin|tests|controllers|deploy|cron)/ { deny all; }
location ~ ^/views/.+\.php$ { deny all; }
location ~ (^|/)(config\.php|\.demo-mode)$|\.sqlite$ { deny all; }
location = /install/ { rewrite ^ /install/index.php last; }
location / { try_files $uri /index.php?$query_string; }
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
The Health page
Health in the menu (/health) shows, one line each, whether the install is looking after itself: the PHP version and extensions, disk space, whether data/ is writable, email, the age of the last backup, the offsite copy, the weekly check, whether an update is waiting, and any file that differs from the version installed. Each line says what to do when it is not fine. The page only reads: it never changes anything and never contacts anything [src: app/src/Health.php].
Connect it to other tools (Zapier, Make, n8n)
RevUp sends a webhook the moment something happens and accepts calls to its API, so Zapier, Make and n8n can connect to it without a plugin. The steps, the events and example recipes are in API.md, in the section "Connect it to Zapier, Make or n8n"; the API page of RevUp on the store shows the same file [src: app/API.md].
New in 4.1.2: sign-in limits
New in 4.1.2 (security update): Two-step codes are now limited (10 wrong per person or per address in 10 minutes), and wrong passwords count per account as well as per address. An installed app whose database does not answer keeps its web installer closed instead of showing the install form. Single sign-on starts a fresh session. On Ownware Cloud, opening the app from the console starts a fresh session.
Wrong passwords are counted per address and, since 4.1.2, per account too (the email typed, in any case, stored only as a hash). After 10 wrong passwords in 10 minutes the sign-in at /login answers "Too many attempts — wait a few minutes and try again." for that address, and for that account from any address. A right password clears both counts [src: app/src/LoginRate.php:40].
Wrong two-step codes (for people who turned two-step sign-in on under /security) are counted per person and per address. After 10 wrong codes in 10 minutes the code step answers "Too many wrong codes. Wait ten minutes, then sign in again. A recovery code works in place of the code." even for the right code, and the half-finished sign-in is dropped, so the password is asked again [src: app/src/LoginRate.php:91]. The right code clears that person's count; an address's count runs out ten minutes after its wrong codes. Before 4.1.2 the code step had no limit.
If you are locked out: wait ten minutes, then sign in again with your password and a current code, or one of your recovery codes (each works once). If you lost both the authenticator and the recovery codes, whoever runs the server can turn two-step sign-in off for you in Revup's database (by default the SQLite file in data/, or your MySQL database): UPDATE users SET totp_secret = '' WHERE email = 'you@example.com'; Then sign in with your password and turn it on again under /security.
Single sign-on now starts a fresh session too [src: app/controllers/v3.php:434].
On Ownware Cloud, opening Revup from the console starts a fresh session [src: app/index.php:58].
The web installer stays closed when the database does not answer: with config.php in place, /install now answers 503 "This app is already installed (config.php exists), but its database is not answering, so the installer stays closed." and writes nothing until the database answers again. Before 4.1.2 a database outage (or a missing SQLite file) showed the install form there, and whoever found it could set up a new administrator. To install again from scratch, remove config.php first [src: app/install/index.php:28].
New in 4.1.3: security update
New in 4.1.3 (security update): no other site can put Revup's pages in a frame; the /mcp address refuses requests from other sites' pages; company sign-in is stricter; and the audit CSV treats one more first character as text. Nothing else changed: no feature, screen, setting or database table, and your data, config.php and data/ are not touched.
No other site can frame these pages. Every page now answers Content-Security-Policy: frame-ancestors 'self' and X-Frame-Options: SAMEORIGIN, so a page on another site cannot show Revup in a hidden frame and trick a signed-in person into pressing a button [src: app/index.php:4]. The installer answers frame-ancestors 'none' and X-Frame-Options: DENY [src: app/install/index.php:12]. The review widget (/widget) is made to sit on your own site and stays frameable, as before [src: app/controllers/v32.php:121]. Any other Revup page framed inside another site of your own now stays empty; link to it instead.
/mcp refuses other sites. A request whose Origin header names another site gets 403 before anything is read. A desktop or server client sends no Origin and is served as before, and so is a page at Revup's own address [src: app/src/Mcp.php:272].
Single sign-on. The sign-in now uses PKCE (S256), so an intercepted sign-in code is of no use on its own; a sign-in that comes back to a different browser session is asked to start again [src: app/src/Oidc.php:228]. With Google as the provider, a company address needs a Google Workspace account of that same domain (the token's hd claim), and Google must report the address as verified, so a personal Google account made with a work address no longer gets in [src: app/src/Oidc.php:215]. Gmail addresses work as before, other providers are unchanged, and password sign-in is not affected.
The audit CSV. A cell that starts with a carriage return now opens in a spreadsheet as text, as cells starting with = + - @ or TAB already did [src: app/src/AuditLog.php:138].
Docker. The Dockerfile now installs PHP's zip extension, so a Docker install can make the backup with files [src: app/Dockerfile:7].
Switched off means off. A person whose account is switched off (users.active = 0) can no longer sign in, and an open session ends at their next page; before 4.1.3 Revup did not read that flag at sign-in or in the session [src: app/src/Auth.php:9] [src: app/src/Auth.php:18].
Also changed, with no effect on a running install: the corner card on a public demo offers only the purchase, and API.md no longer shows the config example of a product we retired.
← Back to Revup · Quickstart · API · Test run