跳到主要内容
Didit 融资 750 万美元,打造身份与欺诈基础设施
Didit
返回博客
博客 · 2026年3月6日

使用 Ruby on Rails 为 Didit 事件构建幂等 Webhook 消费者 (ZH)

构建健壮的 Webhook 消费者对于可靠的数据处理至关重要,特别是对于来自 Didit 等身份验证平台的实时事件。本文将深入探讨如何在 Ruby on Rails 中实现幂等性,确保数据完整性并有效处理重复事件,同时利用 Webhook 签名进行安全验证。.

作者:Didit更新于
idempotent-webhook-consumers-ruby-rails-didit-events.png

确保数据完整性幂等性对于可靠处理 Webhook 事件至关重要,它可以防止重复操作并维护应用程序的一致状态。

利用 Webhook 签名始终验证 Webhook 签名,以确认传入请求的真实性和完整性,防止篡改和欺骗。

使用唯一事件 ID存储并检查 Webhook 有效负载中提供的唯一事件 ID,以有效检测和丢弃重复的交付。

Didit 强大的 Webhook 系统Didit 提供安全的、版本化的 Webhook,带有 HMAC 签名和唯一事件 ID,简化了幂等消费者的实现,并确保了关键身份验证工作流的可靠事件交付。

Webhook 幂等性挑战

Webhook 是一种强大的服务间实时通信机制,使您的应用程序能够即时响应在另一个系统中发生的事件。然而,Webhook 的分布式特性意味着事件有时可能会被多次传递。网络故障、超时或重试都可能导致重复的 Webhook 有效负载。如果处理不当,这些重复事件可能导致您的应用程序出现严重问题,例如创建重复记录、触发冗余操作或损坏数据。

这就是幂等性变得至关重要的原因。幂等操作是指无论执行一次还是多次,都会产生相同结果的操作。对于 Webhook 消费者而言,这意味着设计您的系统,即使 Webhook 有效负载被接收多次,也只处理给定事件一次。在处理来自 Didit 等平台的身份验证结果等敏感数据时,确保幂等性不仅是最佳实践,对于维护数据完整性和系统可靠性也至关重要。

验证 Webhook 签名以确保安全性和真实性

在处理任何 Webhook 有效负载之前,最关键的第一步是验证其真实性。这确保了 Webhook 确实来自预期的发送方(例如 Didit),并且其内容在传输过程中未被篡改。例如,Didit 的 Webhook 在请求头中包含一个 HMAC 签名,该签名使用共享密钥生成。

在您的 Ruby on Rails 应用程序中,您需要从您的 Didit 账户(您可以通过业务控制台或通过 API 以编程方式访问)中检索共享密钥。当 Webhook 到达时,您将使用请求正文和您的密钥计算您自己的 HMAC 签名。然后将此计算出的签名与 Webhook 头中提供的签名进行比较。如果它们不匹配,应立即拒绝请求。


class DiditWebhooksController < ApplicationController
  skip_before_action :verify_authenticity_token

  def create
    # Retrieve the shared secret key from your environment variables
    secret = ENV['DIDIT_WEBHOOK_SECRET']
    payload = request.body.read
    signature = request.headers['X-Didit-Signature'] # Or similar header name

    unless verify_signature(payload, signature, secret)
      head :unauthorized
      return
    end

    # Process the webhook payload after verification
    process_didit_event(JSON.parse(payload))
    head :ok
  end

  private

  def verify_signature(payload, signature, secret)
    digest = OpenSSL::Digest.new('sha256')
    hmac = OpenSSL::HMAC.hexdigest(digest, secret, payload)
    ActiveSupport::SecurityUtils.secure_compare("sha256=#{hmac}", signature)
  end

  def process_didit_event(event_data)
    # ... implementation for processing ...
  end
end

Didit 通过提供一个 secret_shared_key 作为其 Webhook 配置的一部分来简化这一点,该密钥可以通过 API 检索或根据需要进行轮换。这种安全机制对于任何身份验证工作流都至关重要,在这些工作流中,ID 验证或活体检测等结果的完整性是至关重要的。

使用唯一事件标识符实现幂等性

一旦您验证了 Webhook 的真实性,下一步就是确保幂等性。大多数设计良好的 Webhook 系统,包括 Didit 的系统,都会为每个事件提供一个唯一标识符。此 ID 对于检测和防止重复处理至关重要。对于 Didit 的 Webhook(尤其推荐使用 v3 版本),每个事件有效负载都包含一个唯一的 ID,您可以将其用于此目的。

策略是将这些事件 ID 存储在您的数据库中,并在处理之前进行检查。一种常见的模式是创建 EventLogWebhookEvent 模型:


# db/migrate/YYYYMMDDHHMMSS_create_webhook_events.rb
class CreateWebhookEvents < ActiveRecord::Migration[7.x]
  def change
    create_table :webhook_events do |t|
      t.string :event_id, null: false, index: { unique: true }
      t.string :event_type
      t.jsonb :payload
      t.string :status, default: 'pending'
      t.timestamps
    end
  end
end

当 Webhook 到达时:

  1. 从有效负载中提取唯一的 event_id
  2. 尝试使用此 event_id 创建一个新的 WebhookEvent 记录。
  3. 如果由于唯一约束冲突而创建失败(意味着 event_id 已存在),那么您就知道它是重复的,可以安全地忽略它或将其记录为重复。
  4. 如果创建成功,则继续处理事件,并相应地标记其状态。

class DiditWebhooksController < ApplicationController
  # ... (signature verification as above) ...

  def create
    # ... (signature verification) ...

    event_data = JSON.parse(payload)
    event_id = event_data['id'] # Assuming 'id' is the unique event identifier from Didit

    # Use a transaction to ensure atomicity
    ActiveRecord::Base.transaction do
      webhook_event = WebhookEvent.find_or_initialize_by(event_id: event_id)

      if webhook_event.persisted? # If it already exists, it's a duplicate
        Rails.logger.info "Duplicate webhook event received: #{event_id}"
        head :ok
        return
      end

      webhook_event.event_type = event_data['type']
      webhook_event.payload = event_data
      webhook_event.status = 'processing'
      webhook_event.save!

      # Process the event in a background job for long-running tasks
      DiditEventProcessorJob.perform_later(webhook_event.id)
      head :ok
    end
  rescue ActiveRecord::RecordNotUnique # Handle race conditions for event_id
    Rails.logger.warn "Race condition detected for webhook event: #{event_id}. Ignoring."
    head :ok
  rescue JSON::ParserError
    head :bad_request
  rescue => e
    Rails.logger.error "Error processing webhook: #{e.message}"
    head :internal_server_error
  end
end

这种方法与用于实际处理的后台作业相结合,可确保您的 Webhook 端点快速响应,防止发送方重试,同时可靠地处理重复事件。

处理竞态条件和事务

即使使用唯一索引,如果两个相同的 Webhook 几乎同时到达,也可能发生竞态条件。两者都可能在第一个提交其事务之前尝试创建新的 WebhookEvent 记录。为了缓解这种情况:

  • 使用数据库事务:find_or_initialize_by 和后续处理逻辑包装在数据库事务中。这确保了整个操作要么成功要么失败,从而保持数据一致性。
  • 处理 RecordNotUnique准备捕获 ActiveRecord::RecordNotUnique 异常。如果尝试保存新的 WebhookEvent 时发生此异常,则表示存在竞态条件,其中另一个进程已经插入了事件,您可以安全地将其视为重复事件。

对于修改核心应用程序数据的操作,将事务扩展到涵盖这些更改至关重要,或者至少确保处理事件的后台作业也对其修改的应用程序数据实施自己的幂等性检查。

Didit 如何提供帮助

Didit 是一个 AI 原生、开发者优先的身份平台,在设计时充分考虑了可靠性和安全性,使实现健壮的 Webhook 消费者变得更加容易。我们的模块化架构提供了清晰的 API 和安全的 Webhook,这些 Webhook 本身旨在支持幂等处理。

  • 安全 Webhook:Didit 的 Webhook 提供强大的 HMAC 签名(使用您可以管理和轮换的 secret_shared_key)以确保每个事件的真实性和完整性。幂等性的这个关键第一步是内置的。您甚至可以指定 webhook_version(推荐 v3)以获得最佳有效负载结构。
  • 唯一事件标识符:每个 Didit Webhook 事件都包含一个唯一标识符,这使得实现上述去重逻辑变得简单。
  • 可配置的数据保留:使用 Didit,您可以控制验证数据的存储时间。您可以直接在业务控制台或通过 API 设置从 1 个月到 10 年,甚至设置为 null 以实现无限的数据保留策略。这使您能够满足合规性要求(如 GDPR),同时管理您的数据足迹。这也与您的 Webhook 消费者如何存储和管理事件日志相关联。
  • 免费核心 KYC 和模块化设计:Didit 提供免费核心 KYC,让您无需前期成本即可开始构建和测试您的 Webhook 消费者。我们的模块化设计意味着您可以轻松集成特定的身份验证产品——例如用于文档检查的身份验证、用于防欺诈的被动和主动活体检测,或用于年龄验证的年龄估算——并通过 Webhook 接收实时更新。

通过利用 Didit 强大且安全的 Webhook 系统,开发人员可以更多地关注其应用程序的核心逻辑,而更少地关注保护和去重传入事件的复杂性,从而实现更具弹性且值得信赖的身份验证工作流。

准备好开始了吗?

准备好了解 Didit 的实际应用了吗?立即获取免费演示

使用Didit 的免费套餐免费开始验证身份。

身份与欺诈基础设施。

一个 API 即可实现 KYC、KYB、交易监控和钱包筛选。5 分钟即可集成。

让 AI 总结此页面
在 Ruby on Rails 中为 Didit 事件构建幂等 Webhook 消费者.