Compare commits

..

7 Commits

Author SHA1 Message Date
Dhruwang Jariwala
e3f3516fa7 fix: default preview colors (#7277) [Backport to release/4.7] (#7278)
Co-authored-by: Theodór Tómas <theodortomas@gmail.com>
2026-02-17 17:22:57 +05:30
Dhruwang Jariwala
91486f3dc9 fix: reduced default height of input (#7259) [Backport to release/4.7] (#7276) 2026-02-17 11:22:21 +05:30
Dhruwang Jariwala
8b9179bfcc fix: input placeholder color (#7265) [Backport to release/4.7] (#7275)
Co-authored-by: Theodór Tómas <theodortomas@gmail.com>
2026-02-17 11:21:50 +05:30
Dhruwang Jariwala
1b337aeac3 fix: suggest colors has better success copy (#7258) [Backport to release/4.7] (#7273)
Co-authored-by: Theodór Tómas <theodortomas@gmail.com>
2026-02-17 10:42:59 +05:30
Dhruwang Jariwala
cd64848cc5 fix: matrix table preview (#7257) [Backport to release/4.7] (#7272)
Co-authored-by: Theodór Tómas <theodortomas@gmail.com>
2026-02-17 10:42:46 +05:30
Dhruwang Jariwala
42411694a7 fix: fixes number being passed into string attribute (#7255) [Backport to release/4.7] (#7271)
Co-authored-by: Anshuman Pandey <54475686+pandeymangg@users.noreply.github.com>
2026-02-17 10:42:34 +05:30
Dhruwang Jariwala
721d972901 fix: input combobox height (#7256) [Backport to release/4.7] (#7270) 2026-02-17 10:27:16 +05:30
7 changed files with 46 additions and 88 deletions

View File

@@ -30,4 +30,4 @@ export const rateLimitConfigs = {
upload: { interval: 60, allowedPerInterval: 5, namespace: "storage:upload" }, // 5 per minute
delete: { interval: 60, allowedPerInterval: 5, namespace: "storage:delete" }, // 5 per minute
},
} as const;
};

View File

@@ -40,10 +40,7 @@ export const SurveyInline = (props: Omit<SurveyContainerProps, "containerId">) =
isLoadingScript = true;
try {
const scriptUrl = props.appUrl ? `${props.appUrl}/js/surveys.umd.cjs` : "/js/surveys.umd.cjs";
const response = await fetch(
scriptUrl,
process.env.NODE_ENV === "development" ? { cache: "no-store" } : {}
);
const response = await fetch(scriptUrl);
if (!response.ok) {
throw new Error("Failed to load the surveys package");

View File

@@ -405,14 +405,6 @@ const nextConfig = {
},
async rewrites() {
return [
{
source: "/hub",
destination: "https://hub.stldocs.app",
},
{
source: "/hub/:path*",
destination: "https://hub.stldocs.app/:path*",
},
{
source: "/api/packages/website",
destination: "/js/formbricks.umd.cjs",
@@ -490,4 +482,5 @@ const sentryOptions = {
// Runtime Sentry reporting still depends on DSN being set via environment variables
const exportConfig = process.env.SENTRY_AUTH_TOKEN ? withSentryConfig(nextConfig, sentryOptions) : nextConfig;
export default exportConfig;

View File

@@ -1,94 +1,41 @@
---
title: "Rate Limiting"
description: "Current request rate limits in Formbricks"
description: "Rate limiting for Formbricks"
icon: "timer"
---
Formbricks applies request rate limits to protect against abuse and keep API usage fair.
To protect the platform from abuse and ensure fair usage, rate limiting is enforced by default on an IP-address basis. If a client exceeds the allowed number of requests within the specified time window, the API will return a `429 Too Many Requests` status code.
Rate limits are scoped by identifier, depending on the endpoint:
## Default Rate Limits
- IP hash (for unauthenticated/client-side routes and public actions)
- API key ID (for authenticated API calls)
- User ID (for authenticated session-based calls and server actions)
- Organization ID (for follow-up email dispatch)
The following rate limits apply to various endpoints:
When a limit is exceeded, the API returns `429 Too Many Requests`.
| **Endpoint** | **Rate Limit** | **Time Window** |
| ----------------------- | -------------- | --------------- |
| `POST /login` | 30 requests | 15 minutes |
| `POST /signup` | 30 requests | 60 minutes |
| `POST /verify-email` | 10 requests | 60 minutes |
| `POST /forgot-password` | 5 requests | 60 minutes |
| `GET /client-side-api` | 100 requests | 1 minute |
| `POST /share` | 100 requests | 60 minutes |
## Management API Rate Limits
These are the current limits for Management APIs:
| **Route Group** | **Limit** | **Window** | **Identifier** |
| --- | --- | --- | --- |
| `/api/v1/management/*` (except `/api/v1/management/storage`), `/api/v1/webhooks/*`, `/api/v1/integrations/*`, `/api/v1/management/me` | 100 requests | 1 minute | API key ID or session user ID |
| `/api/v2/management/*` (and other v2 authenticated routes that use `authenticatedApiClient`) | 100 requests | 1 minute | API key ID |
| `POST /api/v1/management/storage` | 5 requests | 1 minute | API key ID or session user ID |
## All Enforced Limits
| **Config** | **Limit** | **Window** | **Identifier** | **Used For** |
| --- | --- | --- | --- | --- |
| `auth.login` | 10 requests | 15 minutes | IP hash | Email/password login flow (`/api/auth/callback/credentials`) |
| `auth.signup` | 30 requests | 60 minutes | IP hash | Signup server action |
| `auth.forgotPassword` | 5 requests | 60 minutes | IP hash | Forgot password server action |
| `auth.verifyEmail` | 10 requests | 60 minutes | IP hash | Email verification callback + resend verification action |
| `api.v1` | 100 requests | 1 minute | API key ID or session user ID | v1 management, webhooks, integrations, and `/api/v1/management/me` |
| `api.v2` | 100 requests | 1 minute | API key ID | v2 authenticated API wrapper (`authenticatedApiClient`) |
| `api.client` | 100 requests | 1 minute | IP hash | v1 client API routes (except `/api/v1/client/og` and storage upload override), plus v2 routes that re-use those v1 handlers |
| `storage.upload` | 5 requests | 1 minute | IP hash or authenticated ID | Client storage upload and management storage upload |
| `storage.delete` | 5 requests | 1 minute | API key ID or session user ID | `DELETE /storage/[environmentId]/[accessType]/[fileName]` |
| `actions.emailUpdate` | 3 requests | 60 minutes | User ID | Profile email update action |
| `actions.surveyFollowUp` | 50 requests | 60 minutes | Organization ID | Survey follow-up email processing |
| `actions.sendLinkSurveyEmail` | 10 requests | 60 minutes | IP hash | Link survey email send action |
| `actions.licenseRecheck` | 5 requests | 1 minute | User ID | Enterprise license recheck action |
## Current Endpoint Exceptions
The following routes are currently not rate-limited by the server-side limiter:
- `GET /api/v1/client/og` (explicitly excluded)
- `POST /api/v2/client/[environmentId]/responses`
- `POST /api/v2/client/[environmentId]/displays`
- `GET /api/v2/health`
## 429 Response Shape
v1-style endpoints return:
If a request exceeds the defined rate limit, the server will respond with:
```json
{
"code": "too_many_requests",
"message": "Maximum number of requests reached. Please try again later.",
"details": {}
}
```
v2-style endpoints return:
```json
{
"error": {
"code": 429,
"message": "Too Many Requests"
}
"code": 429,
"error": "Too many requests, Please try after a while!"
}
```
## Disabling Rate Limiting
For self-hosters, rate limiting can be disabled if necessary. We strongly recommend keeping it enabled in production.
For self-hosters, rate limiting can be disabled if necessary. However, we **strongly recommend keeping rate limiting enabled in production environments** to prevent abuse.
Set:
To disable rate limiting, set the following environment variable:
```bash
RATE_LIMITING_DISABLED=1
```
After changing this value, restart the server.
## Operational Notes
- Redis/Valkey is required for robust rate limiting (`REDIS_URL`).
- If Redis is unavailable at runtime, rate-limiter checks currently fail open (requests are allowed through without enforcement).
- Authentication failure audit logging uses a separate throttle (`shouldLogAuthFailure()`) and is intentionally **fail-closed**: when Redis is unavailable or errors occur, audit log entries are **skipped entirely** rather than written without throttle control. This prevents spam while preserving the hash-integrity chain required for compliance. In other words, if Redis is down, no authentication-failure audit logs will be recorded—requests themselves are still allowed (fail-open rate limiting above), but the audit trail for those failures will not be written.
After making this change, restart your server to apply the new setting.

View File

@@ -16,6 +16,8 @@ The Churn Survey is among the most effective ways to identify weaknesses in your
* Follow-up to prevent bad reviews
* Coming soon: Make survey mandatory
## Overview
To run the Churn Survey in your app you want to proceed as follows:
@@ -78,6 +80,13 @@ Whenever a user visits this page, matches the filter conditions above and the re
Here is our complete [Actions manual](/xm-and-surveys/surveys/website-app-surveys/actions/) covering [No-Code](/xm-and-surveys/surveys/website-app-surveys/actions#setting-up-no-code-actions) and [Code](/xm-and-surveys/surveys/website-app-surveys/actions#setting-up-code-actions) Actions.
<Note>
Pre-churn flow coming soon Were currently building full-screen survey
pop-ups. Youll be able to prevent users from closing the survey unless they
respond to it. Its certainly debatable if you want that but you could force
them to click through the survey before letting them cancel 🤷
</Note>
### 5. Select Action in the “When to ask” card
![Select feedback button action](/images/xm-and-surveys/xm/best-practices/cancel-subscription/select-action.webp)

View File

@@ -46,7 +46,13 @@ _Want to change the button color? Adjust it in the project settings!_
Save, and move over to the **Audience** tab.
### 3. Pre-segment your audience
### 3. Pre-segment your audience (coming soon)
<Note>
### Filter by Attribute Coming Soon
We're working on pre-segmenting users by attributes. This manual will be updated in the coming days.
</Note>
Pre-segmentation isn't needed for this survey since you likely want to target all users who cancel their trial. You can use a specific user action, like clicking **Cancel Trial**, to show the survey only to users trying your product.
@@ -56,13 +62,13 @@ How you trigger your survey depends on your product. There are two options:
- **Trigger by Page view:** If you have a page like `/trial-cancelled` for users who cancel their trial subscription, create a user action with the type "Page View." Select "Limit to specific pages" and apply URL filters with these settings:
![Add page URL action](/images/xm-and-surveys/xm/best-practices/improve-trial-cr/action-pageurl.webp)
![Change text content](/images/xm-and-surveys/xm/best-practices/improve-trial-cr/action-pageurl.webp)
Whenever a user visits this page, the survey will be displayed ✅
- **Trigger by Button Click:** In a different case, you have a “Cancel Trial" button in your app. You can setup a user Action with the `Inner Text`:
![Add inner text action](/images/xm-and-surveys/xm/best-practices/improve-trial-cr/action-innertext.webp)
![Change text content](/images/xm-and-surveys/xm/best-practices/improve-trial-cr/action-innertext.webp)
Please have a look at our complete [Actions manual](/xm-and-surveys/surveys/website-app-surveys/actions) if you have questions.

View File

@@ -54,7 +54,13 @@ In the button settings you have to make sure it is set to “External URL”. In
Save, and move over to the “Audience” tab.
### 3. Pre-segment your audience
### 3. Pre-segment your audience (coming soon)
<Note>
## Filter by attribute coming soon. We're working on pre-segmenting users by
attributes. We will update this manual in the next few days.
</Note>
Once you clicked over to the “Audience” tab you can change the settings. In the **Who To Send** card, select “Filter audience by attribute”. This allows you to only show the prompt to a specific segment of your user base.