Action guide
Catch credential stuffing and brute force early
POST
/v1/events · user.login.failedEmit a `user.login.failed` event on every failed sign-in, whether the email exists or not. IP and outcome let you aggregate across a time window and spot distributed attacks before they convert.
When to emit
Inside your auth handler, right after a password check fails or a magic link is rejected. Emit even when the email does not map to a real user, the attacker is still probing.
Example payload
await recalled.events.create({
action: "user.login.failed",
actor: { id: user.id, email: user.email },
organization: user.organizationId,
metadata: {
ip: "203.0.113.42",
userAgent: "Mozilla/5.0",
reason: "value"
},
});Metadata to include
Keep metadata flat and consistent across your service so it plays well with search and CSV exports.
| Key | Purpose |
|---|---|
ip | Source IP address of the request |
userAgent | User agent string of the client |
reason | invalid_password, expired_link, unknown_email, locked_account |
Suggested retention
Keep for at least 90 days, longer if you run anomaly detection or respond to security incidents retroactively.