Flow control
Docs › Flow control
Rate limiting
Skip excess runs to protect a service from work you can discard.
Discard redundant webhook work once a start limit is reached. Inngest checks each matching event before it starts the function and skips excess runs; it does not queue them for later.
Choose this only when you can discard excess runs. If every event must produce a run, use Throttling to queue runs until capacity is available.

Set a limit per customer
This TypeScript v4 function starts at most one run for each company within a four-hour period. The key expression reads company_id from the triggering event. Replace syncCompanyRecord with your application's synchronization code.
import { Inngest } from "inngest";
const inngest = new Inngest({ id: "customer-sync" });
export const syncCompany = inngest.createFunction(
{
id: "sync-company",
triggers: { event: "company/updated" },
rateLimit: {
limit: 1,
period: "4h",
key: "event.data.company_id",
},
},
async ({ event, step }) => {
await step.run("sync-company-record", () =>
syncCompanyRecord(event.data.company_id)
);
}
);import datetime
import inngest
inngest_client = inngest.Inngest(app_id="customer-sync")
@inngest_client.create_function(
fn_id="sync-company",
trigger=inngest.TriggerEvent(event="company/updated"),
rate_limit=inngest.RateLimit(
limit=1,
period=datetime.timedelta(hours=4),
key="event.data.company_id",
),
)
async def sync_company(ctx: inngest.Context) -> None:
async def run_sync() -> None:
await sync_company_record(str(ctx.event.data["company_id"]))
await ctx.step.run("sync-company-record", run_sync)import (
"context"
"net/http"
"time"
"github.com/inngest/inngestgo"
"github.com/inngest/inngestgo/step"
)
type CompanyUpdated struct {
CompanyID string `json:"company_id"`
}
func main() {
client, err := inngestgo.NewClient(inngestgo.ClientOpts{AppID: "customer-sync"})
if err != nil {
panic(err)
}
_, err = inngestgo.CreateFunction(
client,
inngestgo.FunctionOpts{
ID: "sync-company",
RateLimit: &inngestgo.ConfigRateLimit{
Limit: 1,
Period: 4 * time.Hour,
Key: inngestgo.StrPtr("event.data.company_id"),
},
},
inngestgo.EventTrigger("company/updated", nil),
func(ctx context.Context, input inngestgo.Input[CompanyUpdated]) (any, error) {
_, err := step.Run(ctx, "sync-company-record", func(ctx context.Context) (any, error) {
return nil, syncCompanyRecord(ctx, input.Event.Data.CompanyID)
})
return nil, err
},
)
if err != nil {
panic(err)
}
_ = http.ListenAndServe(":8080", client.Serve())
}The key is a Common Expression Language (CEL) expression string, not a JavaScript callback. Each distinct value has its own limit. Without a key, the function uses one shared limit for all matching events.
What happens to an excess event?
The event remains stored in Inngest, but it does not start this function. Inngest does not queue a skipped run for later execution. Rate limiting controls run starts. It does not limit the number of steps running inside an admitted function run.
Inngest uses the generic cell rate algorithm to apply the limit over time. Do not treat the period as a calendar window that resets at a fixed clock time.
You need to… | Use… | What happens at the limit |
|---|---|---|
Discard excess runs | Rate limiting | The event does not start this function. |
Process every run at a controlled pace | Inngest queues the run for later. | |
Limit work running at the same time | Inngest controls active execution. |
Configuration
Option | Meaning |
|---|---|
| Maximum number of function runs that can start in the period. Required. |
| Duration used to apply the limit. Required. TypeScript v4 permits |
| Optional CEL expression evaluated for each triggering event. Each unique result gets its own limit. |
Use idempotency when the goal is to prevent the same event from starting duplicate runs. Use rate limiting when you want to reduce the number of runs over time, including runs from different events with the same key.
Updated Nov 10, 2026
Powered byZunoy