Actions
Onay hattının noktalarında kendi kodunuz: ülkeye, yerel saate, role ya da tutara göre alarm, step-up ve ret; adminler için passkey. Kodsuz bir şablonla ya da birkaç satır JavaScript ile başlayın (sunucu diliniz ne olursa olsun), son gerçek kararlarınıza karşı oynatın, sonra deploy edin, izleyin ve geri alın.
| Tetikleyici | Ne zaman | Kararlar |
|---|---|---|
login.request | Push gönderilmeden önce (POST /login/request), risk motoru ve kurallarınızdan sonra | deny, stepUp, requirePasskey · skipNumberMatch*, overrideBlock* |
risk.check | Sessiz giriş kararı (POST /risk/check) | prompt · allowSilent* |
agent.approval | Bir AI ajanının isteği telefona gitmeden önce (POST /agents/approvals): event.agent, event.request.authorizationDetails, tutarlar, alıcılar | deny, stepUp, requirePasskey (only tightening) |
approval.decided | Onaylandı ya da reddedildi (telefon, passkey, çevrimdışı QR, kurtarma kodu) | — |
shield.decision | Vishing Shield arama sırasında verilen bir onayı değiştirdi; yeniden onaylandı ya da geri alındı | — |
fraud.reported | Sunucunuz bir isteği sahtekârlık olarak bildirdi | — |
device.linked · device.keys_lost · user.unlinked | Telefon bağlandı; anahtarları silindi; 2FA telefondan kapatıldı | — |
passkey.added · passkey.removed · recovery.updated | Passkey'ler; Never Locked Out kurtarma adımları | — |
identity.pre_user_registration | Touchque Login: müşteri kaydolmadan önce (adres ve ad kontrol edilir, henüz bir şey oluşmaz) | deny |
identity.post_login | Touchque Login: müşterinin ilk adımından sonra, ikinci adımdan ve uygulamanın kodundan önce | deny, requireMfa · idToken.setCustomClaim, accessToken.setCustomClaim, user.setAppMetadata, user.setUserMetadata |
identity.post_user_registration | Touchque Login: bir müşteri kaydoldu | — |
Her Action ayrıca bildirim gönderebilir (api.notify.slack | teams | webhook | email), izinli host'larını çağırabilir (api.fetch) ve kullanıcıyı dondurabilir (api.freezeUser). * yetki ister.
pending_approval kalır, önceki sürüm çalışmaya devam eder.| Yetki | Neye izin verir |
|---|---|
skip_number_match | Çalışma alanının kendi istediği numarayı kaldırır: always-challenge ayarı ya da bir erişim kuralının step-up'ı. Risk motorunun istekleri, kritik işlem, push yorgunluğu, işaretli telefon ya da motor kesintisinde asla. |
override_block | Risk motorunun reddini numara eşleşmeli push'a çevirir (asla düz push'a; ülke engeli, erişim kuralı reddi ya da dondurulmuş hesapta asla). |
allow_silent | Şu an bağlı telefonla onaylanmış, cihaza bağlı tanınan bir tarayıcının, yalnız yeni bir ağ/ülke ya da daha eski bir onay (en çok 30 gün, maxAgeSeconds'ı asla aşmadan) yüzünden sorulduğunda telefonu atlamasına izin verir. Kritik işlem, Vishing Shield sinyali, çalışma alanı kapısı, motor kesintisi ya da ALLOW dışında bir kararda asla. |
api.fetch ile sürümün allowedHosts listesine. 50 ms CPU, 16 MB, bir ön tetikleyicinin tüm Action'ları için 300 ms. onFailure step_up ya da deny olmadıkça hatalar girişi asla engellemez. Gizli değerler şifrelidir, yalnız yazılır ve yalnız onları beyan eden sürümlere ulaşır.| Endpoint | Ne yapar |
|---|---|
POST /actions/test | Kayıtlı bir olayda, kendi olayınızda ya da örnekte tek çalıştırma: kararlar, bildirimler (gönderilmez), konsol çıktısı ve kararın ne olacağı. |
POST /actions/dry-run | Taslağı son (en çok 1000) gerçek karara karşı oynatır: kaç tanesi nasıl değişirdi (push→number_match, push→deny…), göndereceği bildirimler, hatalar, p95. |
PUT /actions/{slug} | Deploy (oluştur ya da yeni sürüm, idempotent). POST …/rollback tek çağrıdır. |
GET /actions/{slug}/logs · /metrics | Her canlı çalıştırma; pencere başına çalıştırma, hata, p95, kararlar. |
Bir Action'ın gerekçesi kararın açıklamasının parçasıdır: risk.check nedenlerinde ve step-up'larda { code: "action", text, action }; ret ise 403 { code: "blocked", reason: "action", action, error } döner. Yalnız sunucunuzun bildiğini login isteğinde ya da guard'da context olarak gönderin (telefonda asla gösterilmez).
| id | Tetikleyici | Ne yapar |
|---|---|---|
alert | any | Olay eşleşince Slack, Teams, webhook ya da e-posta (Resend, Postmark, SendGrid) |
block_or_step_up | login.request | Ülke, yerel saat, rol, tutar, işlem ya da riske göre deny, stepUp ya da requirePasskey |
require_passkey_for_role | login.request | Bu roller için yalnız passkey onaylar (context.roles) |
always_ask_when | risk.check | Eşleşen girişte asla sessiz değil |
when: countries, notCountries, hours { from, to, timeZone }, roles, actions, criticalOnly, riskAbove, amount { path, over }, status. Sunucu SDK'ları 5.2.0 (işlem türleri 4.0'da tq.actionTypes'a taşındı).
// Hazır şablon: gece Türkiye'den gelen onaylar için Slack
await tq.actions.secrets.set('SLACK_WEBHOOK_URL', ({}).SLACK_WEBHOOK_URL);
await tq.actions.deploy('night-alert', {
trigger: 'approval.decided',
template: { id: 'alert', params: { channel: 'slack',
when: { countries: ['TR'], hours: { from: 0, to: 6, timeZone: 'Europe/Istanbul' }, status: 'approved' } } },
});
// Kendi kodunuz, tipli; deploy etmeden önce son 500 gerçek karara karşı deneyin
const handler = async (event, api) => {
if (event.context?.roles?.includes('admin')) api.requirePasskey('Admins approve with a passkey');
};
const report = await tq.actions.dryRun({ trigger: 'login.request', handler });
// report.transitions → { 'push→passkey': 12 }
await tq.actions.deploy('admins-use-passkeys', { trigger: 'login.request', handler });
// İzle, geri al
await tq.actions.logs('admins-use-passkeys');
await tq.actions.rollback('admins-use-passkeys');tq.actions.deploy('night-alert', trigger='approval.decided', template={
'id': 'alert', 'params': {'channel': 'slack', 'when': {'countries': ['TR'], 'hours': {'from': 0, 'to': 6}}}})
draft = 'exports.onExecute = async (event, api) => { if (event.context?.amount > 10000) api.stepUp("Over 10,000"); };'
report = tq.actions.dry_run(trigger='login.request', code=draft) # report['transitions']
tq.actions.deploy('big-transfers', trigger='login.request', code=draft)$tq->actions->deploy('night-alert', ['trigger' => 'approval.decided', 'template' => [
'id' => 'alert', 'params' => ['channel' => 'slack', 'when' => ['countries' => ['TR'], 'hours' => ['from' => 0, 'to' => 6]]]]]);
$draft = 'exports.onExecute = async (event, api) => { if (event.context?.amount > 10000) api.stepUp("Over 10,000"); };';
$report = $tq->actions->dryRun(['trigger' => 'login.request', 'code' => $draft]);
$tq->actions->deploy('big-transfers', ['trigger' => 'login.request', 'code' => $draft]);draft := `exports.onExecute = async (event, api) => { if (event.context?.amount > 10000) api.stepUp("Over 10,000"); };`
report, _ := client.Actions.DryRun(touchque.DryRunActionOptions{
ActionDraft: touchque.ActionDraft{Trigger: "login.request", Code: draft}})
_ = report.Transitions // map["push→number_match"]3
client.Actions.Deploy("big-transfers", touchque.DeployActionOptions{Trigger: "login.request", Code: draft})await tq.Actions.DeployAsync("night-alert", new DeployActionOptions
{
Trigger = "approval.decided",
Template = ActionTemplateRef.Of("alert", new { channel = "slack", when = new { countries = new[] { "TR" }, hours = new { from = 0, to = 6 } } }),
});
var draft = "exports.onExecute = async (event, api) => { if (event.context?.amount > 10000) api.stepUp('Over 10,000'); };";
var report = await tq.Actions.DryRunAsync(new DryRunActionOptions { Trigger = "login.request", Code = draft }); // report.Transitions
await tq.Actions.DeployAsync("big-transfers", new DeployActionOptions { Trigger = "login.request", Code = draft });tq.actions().deploy("night-alert", new Actions.DeployOptions().trigger("approval.decided")
.template(Actions.TemplateRef.of("alert", Map.of("channel", "slack",
"when", Map.of("countries", List.of("TR"), "hours", Map.of("from", 0, "to", 6))))));
String draft = "exports.onExecute = async (event, api) => { if (event.context?.amount > 10000) api.stepUp('Over 10,000'); };";
JsonNode report = tq.actions().dryRun(new Actions.Draft().trigger("login.request").code(draft)); // report.get("transitions")
tq.actions().deploy("big-transfers", new Actions.DeployOptions().trigger("login.request").code(draft));tq.actions.deploy("night-alert", trigger: "approval.decided",
template: { id: "alert", params: { channel: "slack", when: { countries: ["TR"], hours: { from: 0, to: 6 } } } })
draft = 'exports.onExecute = async (event, api) => { if (event.context?.amount > 10000) api.stepUp("Over 10,000"); };'
report = tq.actions.dry_run(trigger: "login.request", code: draft) # report["transitions"]
tq.actions.deploy("big-transfers", trigger: "login.request", code: draft)# İmzalı istek (HMAC), bkz. Kimlik Doğrulama
PUT /actions/tr-step-up
{ "trigger": "login.request",
"template": { "id": "block_or_step_up", "params": { "effect": "stepUp", "when": { "countries": ["TR"] } } } }
POST /actions/dry-run
{ "trigger": "login.request", "code": "exports.onExecute = (e, api) => api.requirePasskey('all');", "limit": 500 }// login.request
exports.onExecute = async (event, api) => {
const t = api.time.local(event.client.timeZone || 'Europe/Istanbul');
if (event.client.country === 'TR' && t.hour < 6) {
api.stepUp('Night sign-in from Turkey');
api.notify.slack(`${event.user.id} is signing in from ${event.client.location}`);
}
};{
"trigger": "login.request",
"evaluated": 500,
"matched": 41,
"changed": 38,
"transitions": { "push→number_match": 35, "number_match→deny": 3 },
"notifications": { "slack": 41 },
"errors": 0,
"timeouts": 0,
"p50Ms": 2,
"p95Ms": 6
}