> ## Documentation Index
> Fetch the complete documentation index at: https://docs.scute.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Intent Verification

> Verify user identity via email or SMS with workflow context

# Intent Verification

Verify a user's identity by sending them a magic link or OTP code. Get the result via webhook or a signed JWT on redirect.

## Flow

```
1. POST /workspaces/{id}/children        → create org (once)
2. POST /apps/{app_id}/m2m/token         → get server token (once)
3. POST /verify/{app_id}/verifications/intent  → send verification
4. User clicks link or enters code
5. You get: webhook + redirect with signed JWT
```

***

## Setup

### Create a child workspace

One per organization. Returns API credentials.

```bash theme={null}
curl -X POST https://api.scute.io/v1/workspaces/{workspace_id}/children \
  -H "Authorization: Bearer {workspace_api_key}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme IT",
    "branding_display_name": "Acme IT",
    "branding_color": "#0c2340",
    "branding_logo_url": "https://example.com/logo.png",
    "countries": ["us", "ca"],
    "magic_link_expiry": 900,
    "webhook_url": "https://your-app.com/webhooks/scute"
  }'
```

Returns `app_id` and `api_key_token`. Save both.

Skip this if you're using a single app — just use your existing credentials.

### Get an M2M token

```bash theme={null}
curl -X POST https://api.scute.io/v1/apps/{app_id}/m2m/token \
  -H "Authorization: Bearer {api_key_token}" \
  -H "Content-Type: application/json" \
  -d '{"client_name": "my-backend"}'
```

Use the returned `short_token` (`scm2m_...`) in `X-Authorization` for all verification requests.

***

## Send a verification

```
POST /v1/verify/{app_id}/verifications/intent
X-Authorization: Bearer {short_token}
```

| Field                      | Required  | Description                                                    |
| -------------------------- | --------- | -------------------------------------------------------------- |
| `intent_name`              | yes       | e.g. "Password Reset", "Approve Ticket"                        |
| `method`                   | no        | `email` (default) or `sms`                                     |
| `verification_type`        | no        | `magic_link` or `otp`                                          |
| `redirect_url`             | no        | Where to send the user after verification                      |
| `meta_data.contact_email`  | for email | Recipient email                                                |
| `meta_data.contact_phones` | for sms   | `[{ "phone_number": "+15551234567", "phone_type": "mobile" }]` |
| `meta_data.contact_name`   | no        | Display name                                                   |
| `meta_data.ticket_id`      | no        | Your reference ID — returned in webhooks and JWT               |
| `meta_data.contact_id`     | no        | Your user ID — returned in webhooks and JWT                    |

Add any extra fields to `meta_data` — they're all echoed back.

### Email magic link

```javascript theme={null}
await fetch(`https://api.scute.io/v1/verify/${APP_ID}/verifications/intent`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Authorization": `Bearer ${M2M_TOKEN}`,
  },
  body: JSON.stringify({
    intent_name: "Password Reset",
    method: "email",
    verification_type: "magic_link",
    redirect_url: "https://your-app.com/verified",
    meta_data: {
      contact_email: "user@company.com",
      contact_name: "Sarah",
      ticket_id: "12345",
    },
  }),
});
```

### SMS OTP

```javascript theme={null}
await fetch(`https://api.scute.io/v1/verify/${APP_ID}/verifications/intent`, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Authorization": `Bearer ${M2M_TOKEN}`,
  },
  body: JSON.stringify({
    intent_name: "Confirm Payment",
    method: "sms",
    verification_type: "otp",
    meta_data: {
      contact_email: "user@company.com",
      contact_phones: [{ phone_number: "+15551234567", phone_type: "mobile" }],
    },
  }),
});
```

***

## Getting the result

### Option A: Redirect with JWT

After verification, the user is redirected to:

```
https://your-app.com/verified?scute_token=eyJ...
```

The `scute_token` is an RS256 JWT signed with your app's key. Verify it against your JWKS:

```
GET /v1/auth/{app_id}/jwks
```

Payload:

```json theme={null}
{
  "sub": "user@company.com",
  "verification_id": "uuid",
  "intent": "Password Reset",
  "ticket_id": "12345",
  "contact_id": "1991",
  "verified_at": 1776316476,
  "exp": 1776316536,
  "iss": "scute",
  "aud": "app_id"
}
```

60-second TTL. One-time use.

**Redirect URL template** — set a default in Settings with placeholders:

```
https://your-app.com/verified?ticket={ticket_id}&contact={contact_id}
```

The `scute_token` is appended automatically.

### Option B: Webhook

Events fire to your registered webhook URL.

| Event                            | When                          |
| -------------------------------- | ----------------------------- |
| `verification.*.requested`       | Sent                          |
| `verification.*.verified`        | Confirmed                     |
| `verification.*.expired`         | Timed out                     |
| `verification.*.failed`          | OTP max attempts              |
| `verification.*.denied`          | Contact denied (consent mode) |
| `verification.*.delivery_failed` | SMS/email didn't send         |

Replace `*` with `email` or `sms`.

Payload:

```json theme={null}
{
  "event_type": "verification.sms.verified",
  "data": {
    "verification_id": "uuid",
    "intent": "Password Reset",
    "meta_data": { "ticket_id": "12345", "contact_email": "user@company.com" },
    "timing": {
      "sent_at": "...",
      "confirmed_at": "...",
      "time_to_verify_seconds": 52
    },
    "device": { "os": "iOS", "browser": "Safari" },
    "ip_address": "203.0.113.42"
  }
}
```

Expiration events include `was_opened: true/false` so you know if the link was ever clicked.

Signature: `X-Webhook-Signature: t={timestamp},v1={hmac}` — verify with `HMAC-SHA256("{timestamp}.{payload}", webhook_secret)`.

***

## Verification modes

Set in **Settings > Verification**.

**Dismiss** (default) — confirm identity, redirect. Done.

**Consent** — confirm identity, then ask the user to approve or deny the action. Webhook includes `consent_decision: "approved"` or `"denied"`.

***

## Managing verifications

| Action       | Endpoint                                             |
| ------------ | ---------------------------------------------------- |
| Check status | `GET /v1/verify/{app_id}/verifications/{id}`         |
| Cancel       | `DELETE /v1/verify/{app_id}/verifications/{id}`      |
| Resend       | `POST /v1/verify/{app_id}/verifications/{id}/resend` |
| Deny         | `POST /v1/verify/{app_id}/verifications/{id}/deny`   |

***

## Settings

| Setting               | Default                                         |
| --------------------- | ----------------------------------------------- |
| Tenant mode           | `full` or `verify_only`                         |
| Verification mode     | `dismiss` or `consent`                          |
| Redirect URL template | —                                               |
| Redirect delay        | 4s                                              |
| Success message       | "Your identity has been successfully verified." |
| Rate limit            | 10/hour per contact                             |

***

## Testing

[Scute Testbench](https://testbench.scute.io) — connect with M2M token, search users, send verifications, watch webhooks live.

Local webhooks: `ngrok http 3000`
