The production database,
behind a threshold.
Restores, schema drops, and emergency lockdowns are unrecoverable or close to it. These are the operations where a second approver costs you thirty seconds and saves you a very long weekend.
The operations worth gating
Not everything that touches the database needs a committee. Gating routine work just teaches people to route around the gate. The operations that earn one share two properties: they are hard or impossible to undo, and they are rare enough that a short delay costs nothing.
- Restore from backup — overwrites current state. Almost always run under pressure, which is exactly when a mistaken target gets picked.
- Drop or truncate in a production schema, including the "just this one obsolete table" variety.
- Emergency lockdown — flipping the primary to read-only or cutting connections during a suspected compromise. Correct to have, dangerous to trigger on a hunch alone.
- Failover to another region, when it is manual and not automatic.
- Disabling audit logging or point-in-time recovery — rarely urgent, and a classic step in an attack.
How the flow runs
An admin defines the action once: a name, how many approvals it needs, the webhook it should call, and optionally which members are allowed to raise it at all. During an incident an on-call engineer opens a request and types the reason. Everyone on the team gets it on their phone with the reason attached.
Each approver signs on-device. The signature covers the request identity, the action, the threshold, the reason, and the expiry — so an approval means "I agree to this specific thing, now", and cannot be replayed against a different request later. When the threshold is reached, your endpoint receives the delivery and performs the operation.
# Receiver flips the primary to read-only, only on a verified approval.
@app.post("/hooks/lock-db")
def lock_db():
msg = verify_teamsigner(request, os.environ["LOCK_DB_SECRET"])
if msg is None:
abort(401)
if msg["action_name"] != "LOCK PRODUCTION DATABASE":
abort(400)
# Idempotent: the same request_id must never lock twice.
if seen(msg["request_id"]):
return "", 200
mark_seen(msg["request_id"])
db.execute("ALTER DATABASE app SET default_transaction_read_only = on")
pager.notify(f"prod DB locked — reason: {msg['message']}")
return "", 200<code>verify_teamsigner</code> is the routine from the <a href="/docs">integration docs</a> — HMAC envelope check, signature verification against the canonical string, and distinct-key threshold counting.
Test it before you need it
The failure mode with any emergency control is discovering it is broken at the moment you reach for it. TeamSigner supports test actions: deliveries carry a top-level "test": true so your endpoint runs the full verification path and logs, without actually locking anything.
There is also a drift check. Your verifier holds a list of public keys it trusts; that list goes stale when someone rotates a key, joins, or leaves, and a stale list silently rejects valid approvals at the worst possible moment. Point us at a key-sync URL and we compare your list against ours hourly and tell you when they diverge.
Common questions
Can we limit who is allowed to raise a database request in the first place?
Yes. Each action has an optional initiator allowlist. Leave it empty and any team member can raise a request; set it and only those members can, while approval still requires the full threshold.
What stops someone replaying an old approval?
Every request carries a unique id, an issued-at and an expires-at timestamp, all covered by the signatures. Deliveries also include an idempotency key so your endpoint can reject duplicates.
Does this work with a managed database like RDS or Cloud SQL?
Yes. TeamSigner calls your webhook; what that endpoint does is entirely yours. It can call the AWS or GCP API, run a migration tool, or page a human with a one-time token.
Put a threshold on it.
Define the action, pick how many approvals it needs, point it at your webhook. Free while we're getting started.
Log in & get startedIntegrating? The full webhook contract is in the docs.