Foundations
Ledger Invariants: Create and Attach Velocity Controls to Accounts
In this tutorial we'll cover creating velocity controls in Twisp to ensure balances cannot exceed thresholds.
How-to Guide: Add Velocity Controls in Twisp
Goal: This guide shows you how to create and apply velocity controls to manage transaction limits on accounts or account sets in Twisp.
Prerequisites:
- Access to the Twisp GraphQL API.
- Appropriate permissions to create/manage velocity controls and limits (
velocity.*mutations). - UUIDs of the
AccountorAccountSetyou wish to apply controls to. - Understanding of the specific limits you want to enforce (e.g., daily amount, transaction count).
- (Optional) Familiarity with Common Expression Language (CEL) for defining complex
window,condition, orlimitexpressions.
Key Concepts
- Velocity Control: A container that groups one or more
VelocityLimits. It defines anenforcementaction (Warn, Void, Reject) and an optionalcondition(CEL expression) for when the control applies. - Velocity Limit: Defines a specific rule, such as a maximum amount or count within a defined
window. It includes:name/description: Human-readable identifiers.window: CEL expressions defining the time frame or grouping criteria (e.g., daily, monthly, per merchant). UsesPartitionKey.limit: The actual threshold (amount, layer, direction, start/end times). UsesLimit.currency: The currency the limit applies to (or all if empty).condition: Optional CEL expression for when this specific limit applies.params: Optional parameters needed for dynamic limits (e.g., passing a specific merchant ID).
- Attachment: Linking a
VelocityControlto anAccountorAccountSetmakes the control active for that entity. Parameters required by the associated limits can be provided during attachment.
Steps
Define the Velocity Limit(s):
- Create each specific rule you need using the
createVelocityLimitmutation. - Define its
name,description,window(usingPartitionKeyInput),limit(usingLimitInput),currency, and optionalconditionandparams.
# Example: Create a simple daily spending limit mutation CreateDailyLimit($limitId: UUID!) { createVelocityLimit(input: { velocityLimitId: $limitId name: "Daily Spending Limit" description: "Limit spending to $100 per day." currency: "USD" window: [{alias: "date", value: "context.vars.transaction.effective"}] # Daily window limit: { balance: [{ layer: "SETTLED" # Or PENDING, etc. amount: "decimal('100.00')" normalBalanceType: "DEBIT" # Limit debits }] } # Optional: condition: "context.vars.account.metadata.applyDailyLimit == true" # Optional: params: [{name: "maxAmount", type: DECIMAL}] # If limit amount was dynamic }) { velocityLimitId name } }- Variables:
{ "limitId": "<generate-a-uuid-for-the-limit>" }
- Create each specific rule you need using the
Create the Velocity Control:
- Use the
createVelocityControlmutation. - Define its
name,description,enforcementaction (e.g.,REJECT), and optional overallcondition. - Link the limits created in Step 1 using their
velocityLimitIds in thevelocityLimitIdsarray.
# Example: Create a control using the daily limit from Step 1 mutation CreateSpendingControl($controlId: UUID!, $limitId: UUID!) { createVelocityControl(input: { velocityControlId: $controlId name: "Standard Spending Control" description: "Enforces daily spending limits." enforcement: { action: REJECT } velocityLimitIds: [$limitId] # Link the limit(s) # Optional: condition: "!" + context.vars.account.metadata.exemptFromControl" }) { velocityControlId name limits { velocityLimitId } } }- Variables:
{ "controlId": "<generate-a-uuid-for-the-control>", "limitId": "<uuid-from-step-1>" }
- Use the
Attach the Control to an Account or AccountSet:
- Use the
attachVelocityControlmutation. - Provide the
velocityControlId(from Step 2) and the targetaccountId(UUID of theAccountorAccountSet). - If any attached limits defined
params, provide their values in theparamsJSON object.
# Example: Attach the control to a specific account mutation AttachControlToAccount($controlId: UUID!, $accountId: UUID!) { attachVelocityControl( velocityControlId: $controlId accountId: $accountId # Optional: params: { "maxAmount": "150.00" } # If limit had params ) { velocityControlId name } }- Variables:
{ "controlId": "<uuid-from-step-2>", "accountId": "<target-account-or-set-uuid>" }
- Use the
Verification
- Query the
VelocityControlusing its ID to confirm its limits. - Query the
AccountorAccountSetand check itscontrolsor use theattachedControlsquery to see attached controls. - Post a transaction that should interact with the limit to ensure the expected enforcement action occurs.
- Query the
velocityfield on theAccountorAccountSetto check remaining limits.