Security & Validation
Validate input, protect secrets, verify webhooks, and rate-limit abuse.
Validate input with regex.test
Don't trust user input. Check it before you use it. regex.test uses RE2 syntax and returns false on an invalid pattern (never errors):
// Email
when !regex.test(flow.input, "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$") {
send.text("That doesn't look like a valid email.")
stop
}
// URL
when !regex.test(flow.input, "^https?://") {
send.text("Please send an http(s) URL.")
stop
}
// Digits-only phone (basic)
when !regex.test(flow.input, "^\\+?[0-9]{7,15}$") {
send.text("Please send a valid phone number.")
stop
}
Other useful regex builtins: regex.findAll (extract all matches), regex.match (first match), regex.replace, regex.groups. See Builtins.
The validate library
For quick, dependency-free checks, the validate library wraps common predicates:
import validate from "validate"
let { valid, reason } = await validate.email_check(s: flow.input)
when !valid {
send.text(`Invalid email: ${reason}`)
stop
}
The validate heuristics catch obvious mistakes but aren't RFC-compliant. For strict validation use regex.test, and always do authoritative checks server-side (e.g. send a real confirmation email).
Secrets in global.*
Every secret — API keys, IPN secrets, signing keys, admin ids — belongs in a global.* variable whose value you set in the Variables panel.
global api_key: String = "" // set in the Variables panel
global signing_secret: String = ""
global admin_id: Number = 0
res = http.get(url: "https://api.example.com/data", bearer: global.api_key) on error { stop }
Never hardcode a secret in your .botgami source, a URL, a header value, or a webhook secret. The webhook compiler rejects inline plaintext secrets outright. Reference global.* instead.
Hashing and HMAC
Use crypto.* to hash, sign, and verify:
set { flow.hash = crypto.sha256(flow.token) } // hash
set { flow.sig = crypto.hmacSha256(flow.payload, global.signing_secret) } // sign a request
set { flow.nonce = crypto.randomToken(16) } // random secret/nonce
set { flow.masked = str.mask(user.card, 4) } // safe display
crypto.md5 is for checksums/etags only — never for security.
Verify webhooks
Inbound webhooks must be verified. Use verify: hmac (signed bodies), verify: token (shared secret header), or verify: data (echoed secret). Then re-verify the event against the source API before acting. Never authenticate on request.origin — it's spoofable.
flow ipn on webhook("provider",
verify: hmac, sig_algo: sha256, header: "X-Signature",
secret: global.signing_secret, methods: [post]
) with body: { event: String } {
// signature already verified; still re-check important state via the API
respond.json({ ok: true })
}
Full detail: Statement Reference → Webhooks and Payments & webhooks.
Rate limiting
Throttle expensive or abusable actions with the flow.rateLimit recipe:
flow.rateLimit(key: `redeem:${user.id}`, max: 3) {
body { /* the protected action */ }
on_limited { send.text("⏳ Too many attempts — try again later.") }
}
See Pagination & limits and Recipes.
Anti-abuse cooldowns
For per-user, per-target cooldowns (like the karma bot's "one +rep per person per day"), keep a small ledger and check it before acting:
set { flow.today = time.date() }
set { flow.prior = array.find(user.cooldowns, "uid", flow.target_uid) }
when (!types.isNil(flow.prior)) && (flow.prior.day == flow.today) {
send.text("⏳ You already did that today.")
} else {
// … perform the action, then record the cooldown …
set {
user.cooldowns = array.reject(user.cooldowns, "uid", flow.target_uid)
user.cooldowns << [{ uid: flow.target_uid, day: flow.today }]
}
}