Getting started
Core resources
Other
Caution
This feature is currently under development. Updates will be provided as soon as they become available.

Verification result via Webhooks

SEEK Pass can deliver verification status through POST requests to partner-registered webhook endpoints. Partners can manage their webhooks by creating and updating them, as well as rotating webhook secrets.

SEEK Pass can provide updates for various events at different stages of a verification workflow. Sample events are listed below:

  • incomplete
  • canceled
  • verified

Each webhook is associated with an automatically generated secret that serves as a shared key between partners and SEEK Pass to ensure secure communication. Our service uses this secret to generate an HMAC-SHA256 signature of the event payload, which is transmitted in the request header (e.g., X-Signature). To mitigate replay attacks, a timestamp is also included in the headers (e.g., X-Timestamp) and incorporated into the HMAC calculation.

Partners can use the shared secret to validate message signatures from SEEK Pass. When receiving a message, partners should verify its authenticity by recalculating the HMAC and comparing it against the header signature. To prevent timing attacks, implement a constant-time comparison method:

require "openssl"
require "active_support/security_utils"

def secure_hmac_compare(data, key, given_hmac)
  calculated_hmac = OpenSSL::HMAC.hexdigest("SHA256", key, data)

  # secure_compare requires equal-length strings
  return false unless calculated_hmac.bytesize == given_hmac.bytesize

  # Constant-time comparison
  ActiveSupport::SecurityUtils.secure_compare(calculated_hmac, given_hmac)
end
const crypto = require("crypto");

function generateHmac(key, data) {
  return crypto.createHmac("sha256", key).update(data, "utf8").digest(); // return Buffer
}

function safeCompare(a, b) {
  // Both must be Buffers and same length
  if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) return false;
  if (a.length !== b.length) return false;

  // Constant-time comparison
  return crypto.timingSafeEqual(a, b);
}

Sample webhook resource request:

# POST /api/partner/v2/webhooks

{
  "name": "SEEK Pass Webhook",          // Optional
  "description": "SEEK Pass Webhook",   // Optional
  "url": "https://example.com/webhook"
}

Sample webhook resource response:

{
  "id": "03a7c560-93cd-4579-a26a-a3b98efa8a49"
  "name": "SEEK Pass Webhook",
  "description": "SEEK Pass Webhook",
  "url": "https://example.com/webhook",
  "secret": "23a8db7b1b368dd0e30eff95b048b994e892000e69a4fbb3ca78ff92e13ad1f7"
}

The webhook event only includes the verification status, so partners should call the verification status API with the request ID to get complete verification result such as fullname, date of birth, and other details.

Sample webhook event:

{
  "event_id": "5c4ac58b-5cf9-40a0-b60a-28c0137663ed",
  "request_id": "7cbc7e51-82f5-43ec-8d63-b7cdf1170425",
  "type": "verification",
  "status": "verified",
  "created_at": "2025-04-15T02:14:01.307391Z"
}

Notes

Caution

Webhook events may be delivered out of order due to factors like network latency. To help you determine the correct sequence, each event payload includes a created_at timestamp. We recommend implementing idempotent event processing since duplicate events may occur. Each event includes an event_id that can be used to identify and skip repeated events.

Info

If your webhook endpoint does not respond successfully (HTTP 200) within 5 seconds, SEEK Pass will retry sending the event up to 5 more times.

Info

SEEK Pass sends verification events from designated IP addresses that we will announce, allowing partners to whitelist these IPs for secure connectivity.