Action guide

Catch credential stuffing and brute force early

POST/v1/events · user.login.failed

Emit 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.

KeyPurpose
ipSource IP address of the request
userAgentUser agent string of the client
reasoninvalid_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.

Related actions