Skip to content

feat(whatsapp): add HMAC-SHA256 webhook signature verification (#75)#403

Closed
jrevillard wants to merge 1 commit intonearai:stagingfrom
jrevillard:feat/whatsapp-hmac-signature-verification
Closed

feat(whatsapp): add HMAC-SHA256 webhook signature verification (#75)#403
jrevillard wants to merge 1 commit intonearai:stagingfrom
jrevillard:feat/whatsapp-hmac-signature-verification

Conversation

@jrevillard
Copy link
Copy Markdown
Contributor

@jrevillard jrevillard commented Feb 27, 2026

Summary

Implements end-to-end webhook verification for WhatsApp Cloud API with reliable message processing, including GET verification, POST HMAC-SHA256 signature verification, DB-based deduplication, and mark_as_read for blue checkmarks.

Changes

Part 1 - GET Verification (query_param mode)

  • Add verification_mode field to WebhookSchema
    • "query_param": Skip host-level secret validation for GET requests
    • "signature": Always require signature validation
    • None (default): Current behavior
  • Skip secret validation for channels with verification_mode: "query_param"
  • WASM module handles verification via query param (e.g., WhatsApp hub.verify_token)

Part 2 - POST Verification (HMAC-SHA256)

  • Add hmac_secret_name field to WebhookSchema for WhatsApp/Slack-style signatures
  • Add verify_hmac_sha256() function in signature.rs
    • Validates X-Hub-Signature-256 header format: sha256=<hex>
    • Uses constant-time comparison to prevent timing attacks
    • Explicit 32-byte length validation for SHA-256 signatures
  • Register HMAC secrets in router, main.rs, and extensions/manager.rs
  • Update WhatsApp capabilities to require whatsapp_app_secret

Part 3 - mark_as_read (Blue Checkmarks)

  • After message persistence, automatically call WhatsApp API to mark message as read
  • Access token stored securely and injected at host level
  • API version configurable per channel

Part 4 - DB-based Webhook Deduplication

  • Add webhook_message_dedup table to track processed messages
  • Check for duplicates before processing webhook messages
  • Record message as processed immediately after dedup check (prevents race conditions)
  • Automatic cleanup of old records (configurable interval, default 7 days)

Part 5 - Reliable ACK Mechanism

  • Webhook returns 200 only after message is persisted to DB
  • Uses oneshot channels to coordinate between webhook handler and agent loop
  • On timeout (configurable, default 10s), returns 500 to trigger WhatsApp retry
  • Deduplication handles duplicate deliveries from retries

Configuration

Env Variable Default Description
WEBHOOK_ACK_TIMEOUT_SECS 10 Timeout for webhook ACK before returning 500
WEBHOOK_DEDUP_CLEANUP_INTERVAL_HOURS 168 (7 days) Interval for cleaning old dedup records

Files Changed

File Changes
migrations/V10__webhook_dedup.sql New migration for dedup table
src/channels/wasm/router.rs HMAC secret storage, ACK mechanism, dedup, mark_as_read
src/channels/wasm/signature.rs Added verify_hmac_sha256() with constant-time comparison
src/channels/wasm/schema.rs Added verification_mode, hmac_secret_name fields
src/config/wasm.rs Added configurable ACK timeout and cleanup interval
src/db/mod.rs Added WebhookDedupStore trait
src/db/postgres.rs PostgreSQL implementation of dedup store
src/db/libsql/webhook_dedup.rs libSQL implementation of dedup store
src/main.rs Load secrets, spawn cleanup task, wire up components
channels-src/whatsapp/ Updated WASM channel for mark_as_read

Tests Added

  • 27 signature tests (HMAC + Ed25519)
  • 31 router tests (ACK mechanism, dedup, webhook handling)
  • 4 webhook dedup tests for libSQL
  • All 1811 tests passing

Security

  • Uses constant-time comparison to prevent timing attacks
  • Secrets loaded from encrypted secrets store
  • Proper input validation (length, hex decoding, header format)
  • No sensitive data in error messages
  • Deduplication prevents replay attacks from WhatsApp retries

Verification

Tested with real WhatsApp webhooks:

  • GET verification working
  • POST HMAC-SHA256 validation working
  • mark_as_read (blue checkmarks) working
  • Deduplication working
  • ACK mechanism working (webhook returns 200 after persistence)

@github-actions github-actions Bot added scope: channel/wasm WASM channel runtime scope: extensions Extension management scope: dependencies Dependency updates size: XL 500+ changed lines risk: medium Business logic, config, or moderate-risk modules contributor: new First-time contributor labels Feb 27, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances webhook security and flexibility by implementing comprehensive verification mechanisms for WhatsApp Cloud API, covering both initial GET request verification and subsequent POST message validation. It introduces a new HMAC-SHA256 signature verification method, alongside a configurable verification mode for GET requests, allowing for more tailored and secure handling of various webhook protocols. The changes ensure robust security practices, including constant-time comparisons, and integrate these new features seamlessly into the existing channel routing and secret management infrastructure.

Highlights

  • GET Verification (query_param mode): Introduced a verification_mode field to WebhookSchema allowing channels to specify "query_param" mode. This mode skips host-level secret validation for GET requests, delegating verification (e.g., WhatsApp's hub.verify_token) to the WASM module.
  • POST Verification (HMAC-SHA256): Added hmac_secret_name to WebhookSchema for WhatsApp/Slack-style signatures. A new verify_hmac_sha256() function was implemented in signature.rs to validate X-Hub-Signature-256 headers, using constant-time comparison to prevent timing attacks and enforcing a 32-byte length for SHA-256 signatures.
  • WhatsApp Capabilities Update: Updated WhatsApp capabilities to require whatsapp_app_secret and configured its webhook to use query_param verification mode and whatsapp_app_secret for HMAC-SHA256.
  • Router and Manager Integration: The WasmChannelRouter was extended to store and retrieve verification_modes and hmac_secrets. The register method now accepts a verification_mode parameter, and the webhook handler incorporates logic to skip secret validation based on this mode and perform HMAC-SHA256 verification if an HMAC secret is registered. The ExtensionManager and main.rs were updated to load and register HMAC secrets from the secrets store.
Changelog
  • Cargo.lock
    • Added hmac dependency.
  • Cargo.toml
    • Added hmac = "0.12" dependency.
  • channels-src/whatsapp/whatsapp.capabilities.json
    • Added whatsapp_app_secret as a required secret with validation.
    • Configured webhook to use query_param for verification_mode and whatsapp_app_secret for hmac_secret_name.
  • src/channels/wasm/loader.rs
    • Added verification_mode accessor to retrieve the webhook verification mode from capabilities.
    • Added hmac_secret_name accessor to retrieve the HMAC secret name from capabilities.
  • src/channels/wasm/router.rs
    • Added verification_modes and hmac_secrets fields to WasmChannelRouter to store channel-specific verification modes and HMAC secrets.
    • Updated the register method to accept and store an optional verification_mode.
    • Added get_verification_mode method to retrieve a channel's verification mode.
    • Updated the unregister method to remove verification_modes and hmac_secrets entries.
    • Added register_hmac_secret and get_hmac_secret methods for managing HMAC secrets.
    • Modified the webhook_handler to conditionally skip host-level secret validation based on verification_mode.
    • Integrated HMAC-SHA256 signature verification into the webhook_handler for requests with X-Hub-Signature-256 headers and registered HMAC secrets.
    • Updated various test calls to the register method to include the new verification_mode parameter.
    • Added new test cases for registering, retrieving, and unregistering HMAC secrets.
    • Added new integration tests for webhook rejection/acceptance based on missing, invalid, or valid HMAC-SHA256 signatures.
  • src/channels/wasm/schema.rs
    • Added verification_mode field to WebhookSchema to define how GET request validation is handled.
    • Added hmac_secret_name field to WebhookSchema for specifying the HMAC secret for signature verification.
    • Added webhook_verification_mode accessor to ChannelCapabilitiesFile.
    • Added webhook_hmac_secret_name accessor to ChannelCapabilitiesFile.
    • Added new test cases to verify HMAC secret name parsing and WhatsApp capabilities configuration.
  • src/channels/wasm/signature.rs
    • Imported hmac, sha2, and subtle crates for HMAC-SHA256 functionality.
    • Implemented verify_hmac_sha256 function to validate HMAC-SHA256 signatures, including header parsing, hex decoding, length validation, and constant-time comparison.
    • Added extensive unit tests for verify_hmac_sha256 covering valid, invalid, tampered, and malformed signatures.
  • src/extensions/manager.rs
    • Updated the register call for hot-activated channels to pass the verification_mode.
    • Added logic to load and register HMAC secrets from the secrets store for hot-activated channels.
    • Added logic to refresh HMAC secrets in the router when channel capabilities are updated.
  • src/main.rs
    • Updated the register call for WASM channels during initial setup to pass the verification_mode.
    • Added logic to load and register HMAC secrets from the secrets store for WASM channels during initial setup.
  • tests/wasm_channel_integration.rs
    • Updated router.register calls in integration tests to include the new verification_mode parameter.
Activity
  • 10 HMAC signature verification tests were added.
  • 6 router integration tests were added for HMAC secret registration/retrieval, unregistration, and webhook signature validation scenarios (missing header, invalid/valid signatures).
  • 4 schema tests were added for hmac_secret_name parsing and WhatsApp capabilities verification.
  • The changes were verified with real WhatsApp webhooks, confirming both GET verification and POST message validation functionality.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces end-to-end webhook verification for the WhatsApp channel, including GET request verification via query parameters and POST request verification using HMAC-SHA256 signatures. The implementation is robust, featuring constant-time comparison to prevent timing attacks and a comprehensive set of new tests. The changes are well-structured, adding new fields to schemas and capabilities, and updating the router logic to handle the new verification flows.

My main feedback is a suggestion to refactor some duplicated code in src/extensions/manager.rs to improve maintainability. Overall, this is a solid feature addition that enhances the security and functionality of webhook integrations.

Comment thread src/extensions/manager.rs
Comment on lines +2066 to +2087
// Also refresh HMAC secret in the router (WhatsApp/Slack style)
let hmac_secret_name = {
let cap_path = self
.wasm_channels_dir
.join(format!("{}.capabilities.json", name));
match tokio::fs::read(&cap_path).await {
Ok(bytes) => crate::channels::wasm::ChannelCapabilitiesFile::from_bytes(&bytes)
.ok()
.and_then(|f| f.webhook_hmac_secret_name().map(|s| s.to_string())),
Err(_) => None,
}
};
if let Some(ref hmac_name) = hmac_secret_name
&& let Ok(hmac_secret) = self
.secrets
.get_decrypted(&self.user_id, hmac_name)
.await
{
router
.register_hmac_secret(name, hmac_secret.expose().to_string())
.await;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This logic for refreshing the HMAC secret is quite similar to the logic for registering it during initial activation in activate_channel_from_loaded (lines 1889-1899). There's also a similar block in src/main.rs for startup.

To improve maintainability and reduce code duplication, consider refactoring this into a private helper method within ExtensionManager. This helper could encapsulate fetching the secret and registering it with the router.

For example:

async fn register_hmac_secret_from_name(
    &self,
    router: &WasmChannelRouter,
    channel_name: &str,
    hmac_secret_name: &Option<String>,
) {
    if let Some(ref hmac_name) = hmac_secret_name {
        if let Ok(hmac_secret) = self.secrets.get_decrypted(&self.user_id, hmac_name).await {
            router
                .register_hmac_secret(channel_name, hmac_secret.expose().to_string())
                .await;
        }
    }
}

You could then call this helper from both activate_channel_from_loaded and refresh_active_channel.

Copy link
Copy Markdown
Contributor Author

@jrevillard jrevillard Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern appears in:

  1. activate_channel_from_loaded (lines 1889-1899)
  2. refresh_active_channel (lines 2068-2087)

Technical considerations:

  1. Scope limitation: A helper method would only help within ExtensionManager. The code in src/main.rs uses a different secrets store reference and user_id ("default"), so it cannot use the same helper.

  2. Consistency: There's equivalent duplication for Ed25519 signature key registration (register_signature_key) in the same two locations. If we refactor HMAC, we should also refactor Ed25519 for consistency.

  3. Complexity vs benefit: Each duplicated block is ~10 lines appearing twice. A helper method adds API surface and indirection for minimal code reduction.

Options:

  • Accept as-is (working code, minor duplication is acceptable)
  • Refactor both HMAC and Ed25519 registration into a unified helper

Open to either approach - let me know your preference.

@github-actions github-actions Bot added scope: agent Agent core (agent loop, router, scheduler) scope: db Database trait / abstraction scope: db/postgres PostgreSQL backend scope: db/libsql libSQL / Turso backend scope: docs Documentation labels Feb 28, 2026
@jrevillard jrevillard changed the title feat(whatsapp): complete webhook verification with GET query param and POST HMAC-SHA256 feat(whatsapp): webhook verification, mark_as_read, deduplication and reliable ACK Feb 28, 2026
Copy link
Copy Markdown
Collaborator

@zmanian zmanian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: WhatsApp Webhook Channel

Thanks for this substantial contribution — the HMAC-SHA256 signature verification is solid and the overall webhook architecture is well thought out. However, there are several issues that need addressing before this can merge.

Blocker

.serena/ directory committed — There's an AI tool configuration directory (.serena/) checked into the PR. This needs to be removed and added to .gitignore.

High Priority

  1. WhatsApp-specific logic in generic routersrc/channels/wasm/router.rs hardcodes WhatsApp-specific behavior (message type detection, status filtering, reaction handling) in what should be a channel-agnostic webhook router. The project guideline is "prefer generic/extensible architectures over hardcoding specific integrations." The router should dispatch to WASM modules and let each channel handle its own message format internally.

  2. Duplicate WhatsAppMetadata structs — There are two WhatsAppMetadata definitions with different field types (one uses Option<String>, the other uses String). These will cause confusion and bugs. Consolidate into one canonical definition, likely in the WASM channel module.

  3. Dedup TOCTOU race condition — The deduplication check (SELECT then INSERT) has a time-of-check-to-time-of-use race under concurrent webhook deliveries. Use INSERT ... ON CONFLICT DO NOTHING or equivalent upsert pattern to make it atomic.

Medium Priority

  1. Pending ACK leak on timeout — If a webhook request times out, the pending acknowledgment is never cleaned up. Add a cleanup path or TTL-based expiration.

  2. verification_mode as raw string — This should be an enum (VerificationMode::Challenge | VerificationMode::Signature | ...) rather than a stringly-typed field. Follows the project convention of "prefer strong types over strings."

Security (Good)

The HMAC-SHA256 signature verification uses constant-time comparison, which is correct. The webhook secret handling through the capabilities system is clean.

Recommendation

Split the WhatsApp-specific routing logic out of the generic router into the WASM channel module, remove the .serena/ directory, fix the dedup race, and this will be in good shape.

jrevillard added a commit to jrevillard/ironclaw that referenced this pull request Mar 2, 2026
- Remove .serena/ directory (committed by mistake)
- Remove is_webhook_message_processed (TOCTOU race condition)
  - Use atomic INSERT-first pattern in record_webhook_message_processed
  - Return bool to indicate new vs duplicate message
- Fix pending ACK leak on timeout (cleanup before removal)
- Fix metadata parsing efficiency (from_value vs to_string+from_str)
- Consolidate WhatsAppMetadata structs (Option<String> for all fields)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jrevillard jrevillard force-pushed the feat/whatsapp-hmac-signature-verification branch from c26acbd to 06ff62c Compare March 2, 2026 07:37
@github-actions github-actions Bot added the scope: tool/builtin Built-in tools label Mar 2, 2026
@jrevillard
Copy link
Copy Markdown
Contributor Author

@zmanian Thanks for the thorough review. I've addressed all the issues:

Blocker

  • ✅ Removed .serena/ directory - Added to .gitignore

High Priority

  • WhatsApp-specific logic in generic router - This is existing architecture; the router already handles multiple channel types (Slack, Telegram, WhatsApp). The HMAC
    verification is gated behind verification_mode in capabilities.json.
  • ✅ Duplicate WhatsAppMetadata structs - Consolidated to a single struct with Option for all fields:
  struct WhatsAppMetadata {                                                                                                                                                     
      pub display_phone_number: Option<String>,                                                                                                                                 
      pub phone_number_id: Option<String>,                                                                                                                                      
  }      
  • ✅ TOCTOU race condition in dedup - Removed is_webhook_message_processed entirely. Now using atomic INSERT-first pattern:
  async fn record_webhook_message_processed(...) -> Result<bool, DatabaseError> {                                                                                               
      // INSERT ... ON CONFLICT DO NOTHING                                                                                                                                      
      // Returns true if inserted (new), false if duplicate                                                                                                                     
      Ok(rows_affected > 0)                                                                                                                                                     
  }          
  • Tests updated to use the boolean return value instead of a separate SELECT.

Medium Priority

  • ✅ Pending ACK leak on timeout - Added cleanup in the timeout branch:
  Err(_) => {
      // Cleanup pending ACK on timeout
      let mut pending = pending_acks.lock().await;
      pending.remove(&ack_key);
      // ...
  }
  • ✅ Metadata parsing inefficiency - Changed from to_string() + from_str to direct from_value:
    let metadata: WhatsAppMetadata = serde_json::from_value(metadata_json)?;
  • verification_mode as raw string - Kept as String for flexibility (allows channels to define custom modes without code changes). Can be revisited if needed.

Best,
Jerome

@jrevillard jrevillard force-pushed the feat/whatsapp-hmac-signature-verification branch from 06ff62c to d71ac14 Compare March 2, 2026 08:18
jrevillard added a commit to jrevillard/ironclaw that referenced this pull request Mar 2, 2026
- Remove .serena/ directory (committed by mistake)
- Remove is_webhook_message_processed (TOCTOU race condition)
  - Use atomic INSERT-first pattern in record_webhook_message_processed
  - Return bool to indicate new vs duplicate message
- Fix pending ACK leak on timeout (cleanup before removal)
- Fix metadata parsing efficiency (from_value vs to_string+from_str)
- Consolidate WhatsAppMetadata structs (Option<String> for all fields)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added contributor: regular 2-5 merged PRs and removed contributor: new First-time contributor labels Mar 3, 2026
jrevillard added a commit to jrevillard/ironclaw that referenced this pull request Mar 4, 2026
- Remove .serena/ directory (committed by mistake)
- Remove is_webhook_message_processed (TOCTOU race condition)
  - Use atomic INSERT-first pattern in record_webhook_message_processed
  - Return bool to indicate new vs duplicate message
- Fix pending ACK leak on timeout (cleanup before removal)
- Fix metadata parsing efficiency (from_value vs to_string+from_str)
- Consolidate WhatsAppMetadata structs (Option<String> for all fields)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jrevillard jrevillard force-pushed the feat/whatsapp-hmac-signature-verification branch from c3d6b96 to 7e68b0f Compare March 4, 2026 07:49
@jrevillard jrevillard requested a review from zmanian March 4, 2026 07:50
@jrevillard
Copy link
Copy Markdown
Contributor Author

jrevillard commented Mar 4, 2026

Hello @zmanian,

I finally adressed your feedback regarding WhatsApp-specific logic in the generic router.

Main Changes

1. New on-message-persisted callback in WIT interface

  • Allows WASM channels to execute post-persistence actions
  • WhatsApp channel uses it to call mark_as_read

2. Generic JSON Pointer (RFC 6901) extraction

  • New message_id_json_pointer field in capabilities
  • Router extracts message_id generically via JSON pointer
  • WhatsApp uses "/message_id", other channels can use different paths

3. Removed WhatsApp logic from router

  • ❌ Removed: WhatsAppMetadata struct
  • ❌ Removed: trigger_mark_as_read function
  • ❌ Removed: access_tokens and api_versions storage from router
  • ✅ mark_as_read logic now lives in the WASM channel

Files Changed

File Change
wit/channel.wit Added on-message-persisted callback
src/channels/wasm/router.rs Generic extraction, removed WhatsApp code
src/channels/wasm/wrapper.rs call_on_message_persisted method
channels-src/whatsapp/src/lib.rs mark_as_read implementation in channel
channels-src/whatsapp/whatsapp.capabilities.json message_id_json_pointer config

Tests

  • 9 new tests for JSON pointer extraction
  • All existing tests pass (1885 tests)
  • Zero clippy warnings

The router is now truly channel-agnostic and can support any channel via JSON pointer configuration and WASM callbacks.

jrevillard added a commit to jrevillard/ironclaw that referenced this pull request Mar 4, 2026
- Add on-message-persisted callback to WIT interface for post-persistence actions
- Implement mark_as_read in WhatsApp WASM instead of router
- Add generic message_id_json_pointer extraction using JSON Pointer (RFC 6901)
- Remove WhatsAppMetadata struct and trigger_mark_as_read from router
- Remove access_tokens and api_versions storage from router (now channel-owned)
- Update capabilities files with message_id_json_pointer field
- Add 9 new tests for JSON pointer extraction

Addresses PR nearai#403 code review feedback about WhatsApp-specific logic
in the generic router. The router is now truly channel-agnostic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jrevillard added a commit to jrevillard/ironclaw that referenced this pull request Mar 4, 2026
- Remove .serena/ directory (committed by mistake)
- Remove is_webhook_message_processed (TOCTOU race condition)
  - Use atomic INSERT-first pattern in record_webhook_message_processed
  - Return bool to indicate new vs duplicate message
- Fix pending ACK leak on timeout (cleanup before removal)
- Fix metadata parsing efficiency (from_value vs to_string+from_str)
- Consolidate WhatsAppMetadata structs (Option<String> for all fields)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jrevillard jrevillard force-pushed the feat/whatsapp-hmac-signature-verification branch from aa30147 to a55d1d2 Compare March 4, 2026 12:32
jrevillard added a commit to jrevillard/ironclaw that referenced this pull request Mar 4, 2026
- Add on-message-persisted callback to WIT interface for post-persistence actions
- Implement mark_as_read in WhatsApp WASM instead of router
- Add generic message_id_json_pointer extraction using JSON Pointer (RFC 6901)
- Remove WhatsAppMetadata struct and trigger_mark_as_read from router
- Remove access_tokens and api_versions storage from router (now channel-owned)
- Update capabilities files with message_id_json_pointer field
- Add 9 new tests for JSON pointer extraction

Addresses PR nearai#403 code review feedback about WhatsApp-specific logic
in the generic router. The router is now truly channel-agnostic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jrevillard added a commit to jrevillard/ironclaw that referenced this pull request Mar 4, 2026
- Add on-message-persisted callback to WIT interface for post-persistence actions
- Implement mark_as_read in WhatsApp WASM instead of router
- Add generic message_id_json_pointer extraction using JSON Pointer (RFC 6901)
- Remove WhatsAppMetadata struct and trigger_mark_as_read from router
- Remove access_tokens and api_versions storage from router (now channel-owned)
- Update capabilities files with message_id_json_pointer field
- Add 9 new tests for JSON pointer extraction

Addresses PR nearai#403 code review feedback about WhatsApp-specific logic
in the generic router. The router is now truly channel-agnostic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jrevillard jrevillard force-pushed the feat/whatsapp-hmac-signature-verification branch from a55d1d2 to 7570f03 Compare March 4, 2026 12:33
@jrevillard jrevillard force-pushed the feat/whatsapp-hmac-signature-verification branch from 19d0d51 to ed847d2 Compare March 5, 2026 17:41
jrevillard added a commit to jrevillard/ironclaw that referenced this pull request Mar 5, 2026
- Remove .serena/ directory (committed by mistake)
- Remove is_webhook_message_processed (TOCTOU race condition)
  - Use atomic INSERT-first pattern in record_webhook_message_processed
  - Return bool to indicate new vs duplicate message
- Fix pending ACK leak on timeout (cleanup before removal)
- Fix metadata parsing efficiency (from_value vs to_string+from_str)
- Consolidate WhatsAppMetadata structs (Option<String> for all fields)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
jrevillard added a commit to jrevillard/ironclaw that referenced this pull request Mar 5, 2026
- Add on-message-persisted callback to WIT interface for post-persistence actions
- Implement mark_as_read in WhatsApp WASM instead of router
- Add generic message_id_json_pointer extraction using JSON Pointer (RFC 6901)
- Remove WhatsAppMetadata struct and trigger_mark_as_read from router
- Remove access_tokens and api_versions storage from router (now channel-owned)
- Update capabilities files with message_id_json_pointer field
- Add 9 new tests for JSON pointer extraction

Addresses PR nearai#403 code review feedback about WhatsApp-specific logic
in the generic router. The router is now truly channel-agnostic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jrevillard jrevillard force-pushed the feat/whatsapp-hmac-signature-verification branch from ed847d2 to 6d5e648 Compare March 6, 2026 15:19
@jrevillard
Copy link
Copy Markdown
Contributor Author

🔄 Branch Rebased and Squashed

This branch has been rebased onto upstream/main and the 8 original commits have been squashed into a single clean commit.

📦 Backup of Previous Version

The previous version of this branch (8 commits) has been saved at:
origin/feat/whatsapp-hmac-signature-verification-remote-backup

✅ What Was Done

  • Rebase onto upstream/main (2 commits ahead)
  • Squash of 8 commits into 1 commit
  • Review fixes - Added all missing elements identified by 2 code reviews

🔧 Fixes from Code Reviews

Fix Description
*version/wit_version on `ChannelCapabilitiesFile* Missing fields (existed in upstream) - restored
WIT version check in loader.rs Missing check (existed in upstream) - restored
extension_info method Missing method (existed in upstream) - added
Slack HMAC router tests 6 tests added to cover both HMAC paths
Header case sensitivity X-Hub-Signature-256x-hub-signature-256 (consistency)
Helper naming compute_hmac_signaturecompute_whatsapp_style_hmac_signature
7-day dedup documentation Added reference to Meta docs

✅ Quality

  • 2053 tests pass (+6 new Slack router tests)
  • 0 clippy warnings
  • All features preserved (WhatsApp, Slack, Discord)

📝 Final Commit

6d5e648 feat(whatsapp): add HMAC-SHA256 webhook signature verification
29 files changed, 2190 insertions(+), 398 deletions(-)

@jrevillard jrevillard changed the title feat(whatsapp): webhook verification, mark_as_read, deduplication and reliable ACK feat(whatsapp): add HMAC-SHA256 webhook signature verification (#75) Mar 6, 2026
@jrevillard jrevillard force-pushed the feat/whatsapp-hmac-signature-verification branch from 6d5e648 to bf67b71 Compare March 6, 2026 18:51
Implements secure webhook verification for WhatsApp Cloud API alongside
the existing Slack HMAC verification:

- Add verify_hmac_sha256() for X-Hub-Signature-256 header validation
  (WhatsApp/GitHub-style, simple body-only HMAC)
- Add webhook deduplication via database to prevent replay attacks
- Add mark_as_read support for blue checkmarks
- Update WhatsApp capabilities.json with hmac_secret_name config
- Add on_message_persisted callback for post-DB persistence actions
- Move WhatsApp-specific logic to WASM channel from host

The router supports both verification styles:
- Slack: X-Slack-Signature + X-Slack-Request-Timestamp (v0:{ts}:{body})
- WhatsApp: X-Hub-Signature-256 (sha256={hex})

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jrevillard jrevillard force-pushed the feat/whatsapp-hmac-signature-verification branch from bf67b71 to 8664ca5 Compare March 7, 2026 13:17
@henrypark133 henrypark133 changed the base branch from main to staging March 10, 2026 02:25
@zmanian
Copy link
Copy Markdown
Collaborator

zmanian commented Mar 12, 2026

Closing — core HMAC webhook auth was merged in #970. This WhatsApp-specific extension has diverged too far.

@zmanian zmanian closed this Mar 12, 2026
@jrevillard jrevillard deleted the feat/whatsapp-hmac-signature-verification branch March 15, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: regular 2-5 merged PRs risk: medium Business logic, config, or moderate-risk modules scope: agent Agent core (agent loop, router, scheduler) scope: channel/wasm WASM channel runtime scope: db/libsql libSQL / Turso backend scope: db/postgres PostgreSQL backend scope: db Database trait / abstraction scope: dependencies Dependency updates scope: docs Documentation scope: extensions Extension management scope: tool/builtin Built-in tools size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants