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
Account
orAccountSet
you 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
, orlimit
expressions.
Key Concepts
- Velocity Control: A container that groups one or more
VelocityLimit
s. It defines anenforcement
action (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
VelocityControl
to anAccount
orAccountSet
makes 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
createVelocityLimit
mutation. - Define its
name
,description
,window
(usingPartitionKeyInput
),limit
(usingLimitInput
),currency
, and optionalcondition
andparams
.
# 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
createVelocityControl
mutation. - Define its
name
,description
,enforcement
action (e.g.,REJECT
), and optional overallcondition
. - Link the limits created in Step 1 using their
velocityLimitId
s in thevelocityLimitIds
array.
# 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
attachVelocityControl
mutation. - Provide the
velocityControlId
(from Step 2) and the targetaccountId
(UUID of theAccount
orAccountSet
). - If any attached limits defined
params
, provide their values in theparams
JSON 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
VelocityControl
using its ID to confirm its limits. - Query the
Account
orAccountSet
and check itscontrols
or use theattachedControls
query to see attached controls. - Post a transaction that should interact with the limit to ensure the expected enforcement action occurs.
- Query the
velocity
field on theAccount
orAccountSet
to check remaining limits.