-
Notifications
You must be signed in to change notification settings - Fork 27
Meta Cloud API Whatsapp Integration #2975
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
SmittieC
wants to merge
30
commits into
main
Choose a base branch
from
cs/whatsapp
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
226a18e
Add Meta Cloud API messaging provider for WhatsApp
SmittieC 8808a3d
Resolve WhatsApp phone number ID via Meta Cloud API
SmittieC c2506b0
Use phone_number_id as from identifier for Meta Cloud API WhatsApp ch…
SmittieC 12ff881
Add Meta Cloud API WhatsApp webhook integration
SmittieC 2f6506f
Add X-Hub-Signature-256 verification for Meta Cloud API webhooks
SmittieC 45520d1
Verify Meta webhook signature once using the first channel's app_secret
SmittieC 959106f
Remove unused phone_number_id field from MetaCloudAPIMessage
SmittieC a3fb00a
Fix typo: mesaging_provider -> messaging_provider
SmittieC 9bec7c0
Harden Meta Cloud API webhook and refactor helpers
SmittieC 35f7005
Query MessagingProvider directly in verify_webhook instead of through…
SmittieC 58322c8
Split long messages in MetaCloudAPIService to respect WhatsApp's 4096…
SmittieC 02f330e
Use verify_token hash for efficient DB lookup in Meta webhook verific…
SmittieC 05b60c2
Refactor meta_webhook to module-level functions and add missing tests
SmittieC ee17335
Address PR #2975 review comments
SmittieC d822a95
Merge branch 'main' into cs/whatsapp
SmittieC c6f252d
Merge migrations and fix conflict
SmittieC 86e698f
Add developer docs for Meta Cloud API messaging provider configuration
SmittieC 4d1aa14
Refactor Meta Cloud API webhook to class-based view
SmittieC 2714d88
Add example message structure in extract_message_values docstring
SmittieC 2e893fe
Link to example instead
SmittieC e09de8e
Fix url
SmittieC de26e8a
Update apps/channels/meta_webhook.py
SmittieC 4a5ca39
fix: address PR #2975 review comments
SmittieC 694d11b
Add logging to Meta Cloud API webhook post handler
SmittieC 8fa0ace
Fix flaky test_chatbot_table_redirect_url by clearing cached team slug
SmittieC cc7853b
fix: route Meta webhook messages to their own channel by phone_number_id
SmittieC 75b1220
fix: reject Meta webhook payloads spanning multiple messaging providers
SmittieC 36331f2
Merge branch 'main' into cs/whatsapp
SmittieC aed1d25
Don't log phone number ids
SmittieC 33ba093
Resolve migration number conflicts
SmittieC File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import hashlib | ||
| import hmac | ||
|
|
||
| from django.http import HttpResponse, HttpResponseBadRequest | ||
| from django.utils.crypto import constant_time_compare | ||
|
|
||
| from apps.service_providers.models import MessagingProvider, MessagingProviderType | ||
|
|
||
|
|
||
| def extract_message_values(data: dict) -> list[dict]: | ||
| """Extract value dicts that contain messages from Meta webhook payload. | ||
|
|
||
| See https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/create-webhook-endpoint/ | ||
|
|
||
| See https://developers.facebook.com/documentation/business-messaging/whatsapp/webhooks/overview for an | ||
| example of the payload | ||
| """ | ||
|
|
||
| values = [] | ||
| for entry in data.get("entry", []): | ||
| for change in entry.get("changes", []): | ||
| value = change.get("value", {}) | ||
| if value.get("messages") and value.get("metadata", {}).get("phone_number_id"): | ||
| values.append(value) | ||
| return values | ||
|
|
||
|
|
||
| def verify_webhook(request) -> HttpResponse: | ||
| """Handle the Meta webhook GET verification handshake.""" | ||
| mode = request.GET.get("hub.mode") | ||
| token = request.GET.get("hub.verify_token") | ||
| challenge = request.GET.get("hub.challenge") | ||
|
|
||
| if mode != "subscribe" or not token or not challenge: | ||
| return HttpResponseBadRequest("Verification failed.") | ||
|
|
||
| token_hash = hashlib.sha256(token.encode()).hexdigest() | ||
| exists = MessagingProvider.objects.filter( | ||
| type=MessagingProviderType.meta_cloud_api, | ||
| extra_data__verify_token_hash=token_hash, | ||
| ).exists() | ||
|
|
||
| if exists: | ||
| return HttpResponse(challenge, content_type="text/plain") | ||
|
|
||
| return HttpResponseBadRequest("Verification failed.") | ||
|
|
||
|
|
||
| def verify_signature(payload: bytes, signature_header: str, app_secret: str) -> bool: | ||
| """Verify the X-Hub-Signature-256 header from Meta webhooks.""" | ||
| if not signature_header.startswith("sha256=") or not app_secret: | ||
| return False | ||
|
|
||
| expected_signature = signature_header.removeprefix("sha256=") | ||
| computed = hmac.new( | ||
| app_secret.encode(), | ||
| payload, | ||
| hashlib.sha256, | ||
| ).hexdigest() | ||
| return constant_time_compare(computed, expected_signature) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.