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

# Verify Push API

> Verify Push endpoints exposed by the Devotel CPaaS API

# Verify Push API

Verify Push endpoints exposed by the Devotel CPaaS API

**Base path:** `/api/v1/verify/push`

**Endpoint count:** 4

***

### Verify a push challenge signature

<Note>
  `POST /api/v1/verify/push/challenges/{challengeId}/verify`
</Note>

Verify the device's signature over the challenge's signing string. On match: challenge moves to `approved`, factor flips to `verified`. On mismatch: attempts increment; auto-denied at maxAttempts.

<ParamField path="challengeId" type="string" required>
  —
</ParamField>

<ParamField body="signature" type="string" required>
  The device's base64-encoded signature over the challenge signing string factorId.challengeId.nonce (1–4096 characters).
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://api.orbit.devotel.io/api/v1/verify/push/challenges/{challengeId}/verify" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
      "signature": "c2lnbmF0dXJlX2Jhc2U2NF9wbGFjZWhvbGRlcl9kZW1vX29ubHlfbm90X2FfcmVhbF9zaWduYXR1cmVfdmFsdWU="
    }'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://api.orbit.devotel.io/api/v1/verify/push/challenges/{challengeId}/verify', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
      "signature": "c2lnbmF0dXJlX2Jhc2U2NF9wbGFjZWhvbGRlcl9kZW1vX29ubHlfbm90X2FfcmVhbF9zaWduYXR1cmVfdmFsdWU="
    }),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://api.orbit.devotel.io/api/v1/verify/push/challenges/{challengeId}/verify", headers=headers, json={
      "signature": "c2lnbmF0dXJlX2Jhc2U2NF9wbGFjZWhvbGRlcl9kZW1vX29ubHlfbm90X2FfcmVhbF9zaWduYXR1cmVfdmFsdWU="
    })
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/verify/push/challenges/{challengeId}/verify", bytes.NewBuffer([]byte(`{
      "signature": "c2lnbmF0dXJlX2Jhc2U2NF9wbGFjZWhvbGRlcl9kZW1vX29ubHlfbm90X2FfcmVhbF9zaWduYXR1cmVfdmFsdWU="
    }`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://api.orbit.devotel.io/api/v1/verify/push/challenges/{challengeId}/verify')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {
      "signature": "c2lnbmF0dXJlX2Jhc2U2NF9wbGFjZWhvbGRlcl9kZW1vX29ubHlfbm90X2FfcmVhbF9zaWduYXR1cmVfdmFsdWU="
    }.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://api.orbit.devotel.io/api/v1/verify/push/challenges/{challengeId}/verify');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {
      "signature": "c2lnbmF0dXJlX2Jhc2U2NF9wbGFjZWhvbGRlcl9kZW1vX29ubHlfbm90X2FfcmVhbF9zaWduYXR1cmVfdmFsdWU="
    }
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Register a push factor

<Note>
  `POST /api/v1/verify/push/factors`
</Note>

Register a device's public key for phishing-resistant MFA. Returns a binding challenge the device must sign to activate the factor.

<ParamField body="identity" type="string" required>
  Stable identifier for the end user this device belongs to — your user id, email, or username (1–255 characters).
</ParamField>

<ParamField body="friendlyName" type="string" required>
  Human-readable label for the paired device, shown when the user reviews their registered factors (1–120 characters).
</ParamField>

<ParamField body="algorithm" type="string (enum: ed25519|ecdsa-p256)" required>
  Signature algorithm of the device key pair — `ed25519` or `ecdsa-p256`.
</ParamField>

<ParamField body="publicKey" type="string" required>
  The device's base64-encoded public key. The matching private key never leaves the device; it signs each challenge (1–4096 characters).
</ParamField>

<ParamField body="keyFormat" type="string (enum: raw|spki)" required>
  Encoding of `publicKey` — `raw` for a bare key or `spki` for a SubjectPublicKeyInfo-wrapped key.
</ParamField>

<ParamField body="deviceTokenId" type="string">
  Optional push-token id (FCM, APNs, or Web Push) the service uses to deliver challenge notifications to this device (1–120 characters).
</ParamField>

<ParamField body="metadata" type="object">
  Optional free-form key/value bag — device model, OS version, attestation chain. The caller defines the contract.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://api.orbit.devotel.io/api/v1/verify/push/factors" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
      "identity": "user_1a2b3c",
      "friendlyName": "iPhone 15 Pro",
      "algorithm": "ed25519",
      "publicKey": "3Mv6c0kZ8y2Qb7pL9dWfX1aRtH4sN6uG0eI2oJ5PqE=",
      "keyFormat": "raw",
      "deviceTokenId": "fcm_e8b1c2d3a4f5",
      "metadata": {
        "deviceModel": "iPhone15,3",
        "os": "iOS 17.4"
      }
    }'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://api.orbit.devotel.io/api/v1/verify/push/factors', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
      "identity": "user_1a2b3c",
      "friendlyName": "iPhone 15 Pro",
      "algorithm": "ed25519",
      "publicKey": "3Mv6c0kZ8y2Qb7pL9dWfX1aRtH4sN6uG0eI2oJ5PqE=",
      "keyFormat": "raw",
      "deviceTokenId": "fcm_e8b1c2d3a4f5",
      "metadata": {
        "deviceModel": "iPhone15,3",
        "os": "iOS 17.4"
      }
    }),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://api.orbit.devotel.io/api/v1/verify/push/factors", headers=headers, json={
      "identity": "user_1a2b3c",
      "friendlyName": "iPhone 15 Pro",
      "algorithm": "ed25519",
      "publicKey": "3Mv6c0kZ8y2Qb7pL9dWfX1aRtH4sN6uG0eI2oJ5PqE=",
      "keyFormat": "raw",
      "deviceTokenId": "fcm_e8b1c2d3a4f5",
      "metadata": {
        "deviceModel": "iPhone15,3",
        "os": "iOS 17.4"
      }
    })
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/verify/push/factors", bytes.NewBuffer([]byte(`{
      "identity": "user_1a2b3c",
      "friendlyName": "iPhone 15 Pro",
      "algorithm": "ed25519",
      "publicKey": "3Mv6c0kZ8y2Qb7pL9dWfX1aRtH4sN6uG0eI2oJ5PqE=",
      "keyFormat": "raw",
      "deviceTokenId": "fcm_e8b1c2d3a4f5",
      "metadata": {
        "deviceModel": "iPhone15,3",
        "os": "iOS 17.4"
      }
    }`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://api.orbit.devotel.io/api/v1/verify/push/factors')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {
      "identity": "user_1a2b3c",
      "friendlyName": "iPhone 15 Pro",
      "algorithm": "ed25519",
      "publicKey": "3Mv6c0kZ8y2Qb7pL9dWfX1aRtH4sN6uG0eI2oJ5PqE=",
      "keyFormat": "raw",
      "deviceTokenId": "fcm_e8b1c2d3a4f5",
      "metadata": {
        "deviceModel": "iPhone15,3",
        "os": "iOS 17.4"
      }
    }.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://api.orbit.devotel.io/api/v1/verify/push/factors');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {
      "identity": "user_1a2b3c",
      "friendlyName": "iPhone 15 Pro",
      "algorithm": "ed25519",
      "publicKey": "3Mv6c0kZ8y2Qb7pL9dWfX1aRtH4sN6uG0eI2oJ5PqE=",
      "keyFormat": "raw",
      "deviceTokenId": "fcm_e8b1c2d3a4f5",
      "metadata": {
        "deviceModel": "iPhone15,3",
        "os": "iOS 17.4"
      }
    }
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Issue a push challenge

<Note>
  `POST /api/v1/verify/push/factors/{factorId}/challenges`
</Note>

Mint a server-issued nonce + persist a pending challenge row. The device signs `&lt;factorId&gt;.&lt;challengeId&gt;.&lt;nonce&gt;` and submits the signature via /verify/push/challenges/:challengeId/verify.

<ParamField path="factorId" type="string" required>
  —
</ParamField>

<ParamField body="contextMessage" type="string">
  Human-readable prompt shown in the push notification so the user knows what they are approving, e.g. the action or device (1–280 characters).
</ParamField>

<ParamField body="ttlSeconds" type="integer">
  Lifetime of the issued challenge, in seconds, before it expires. Defaults to 120; range 30–600.
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://api.orbit.devotel.io/api/v1/verify/push/factors/{factorId}/challenges" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{
      "contextMessage": "Approve sign-in from Chrome on macOS",
      "ttlSeconds": 120
    }'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://api.orbit.devotel.io/api/v1/verify/push/factors/{factorId}/challenges', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
      "contextMessage": "Approve sign-in from Chrome on macOS",
      "ttlSeconds": 120
    }),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://api.orbit.devotel.io/api/v1/verify/push/factors/{factorId}/challenges", headers=headers, json={
      "contextMessage": "Approve sign-in from Chrome on macOS",
      "ttlSeconds": 120
    })
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/verify/push/factors/{factorId}/challenges", bytes.NewBuffer([]byte(`{
      "contextMessage": "Approve sign-in from Chrome on macOS",
      "ttlSeconds": 120
    }`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://api.orbit.devotel.io/api/v1/verify/push/factors/{factorId}/challenges')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {
      "contextMessage": "Approve sign-in from Chrome on macOS",
      "ttlSeconds": 120
    }.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://api.orbit.devotel.io/api/v1/verify/push/factors/{factorId}/challenges');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {
      "contextMessage": "Approve sign-in from Chrome on macOS",
      "ttlSeconds": 120
    }
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***

### Revoke a push factor

<Note>
  `POST /api/v1/verify/push/factors/{factorId}/revoke`
</Note>

Mark a factor as revoked. All subsequent challenges are rejected with a 409 + reason=FACTOR\_REVOKED. Irreversible.

<ParamField path="factorId" type="string" required>
  —
</ParamField>

<RequestExample>
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://api.orbit.devotel.io/api/v1/verify/push/factors/{factorId}/revoke" \
      -H "X-API-Key: dv_live_sk_your_key_here" \
      -H "Content-Type: application/json" \
      -d '{}'
    ```

    ```typescript Node.js theme={null}
    import { Orbit } from '@devotel/orbit-sdk'

    const orbit = new Orbit({
      apiKey: process.env.ORBIT_API_KEY!,
    })

    const res = await fetch('https://api.orbit.devotel.io/api/v1/verify/push/factors/{factorId}/revoke', {
      method: 'POST',
      headers: {
        'X-API-Key': process.env.ORBIT_API_KEY!,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({}),
    })
    console.log(await res.json())
    ```

    ```python Python theme={null}
    import os, requests

    headers = {"X-API-Key": os.environ["ORBIT_API_KEY"]}
    headers["Content-Type"] = "application/json"
    r = requests.post("https://api.orbit.devotel.io/api/v1/verify/push/factors/{factorId}/revoke", headers=headers, json={})
    print(r.json())
    ```

    ```go Go theme={null}
    package main

    import (
    	"bytes"
    	"net/http"
    	"os"
    )

    func main() {
    	req, _ := http.NewRequest("POST", "https://api.orbit.devotel.io/api/v1/verify/push/factors/{factorId}/revoke", bytes.NewBuffer([]byte(`{}`)))
    	req.Header.Set("X-API-Key", os.Getenv("ORBIT_API_KEY"))
    	req.Header.Set("Content-Type", "application/json")
    	http.DefaultClient.Do(req)
    }
    ```

    ```ruby Ruby theme={null}
    require 'net/http'
    require 'json'

    uri = URI('https://api.orbit.devotel.io/api/v1/verify/push/factors/{factorId}/revoke')
    req = Net::HTTP::Post.new(uri)
    req['X-API-Key'] = ENV['ORBIT_API_KEY']
    req['Content-Type'] = 'application/json'
    req.body = {}.to_json
    res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
    puts res.body
    ```

    ```php PHP theme={null}
    <?php
    $ch = curl_init('https://api.orbit.devotel.io/api/v1/verify/push/factors/{factorId}/revoke');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'X-API-Key: ' . getenv('ORBIT_API_KEY'),
      'Content-Type: application/json',
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, <<<JSON
    {}
    JSON);
    echo curl_exec($ch);
    ```
  </CodeGroup>
</RequestExample>

***
