-
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
base: main
Are you sure you want to change the base?
Changes from 10 commits
226a18e
8808a3d
c2506b0
12ff881
2f6506f
45520d1
959106f
a3fb00a
9bec7c0
35f7005
58322c8
02f330e
05b60c2
ee17335
d822a95
c6f252d
86e698f
4d1aa14
2714d88
2e893fe
e09de8e
de26e8a
4a5ca39
694d11b
8fa0ace
cc7853b
75b1220
36331f2
aed1d25
33ba093
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -235,14 +235,28 @@ def clean_number(self): | |
| number_obj = phonenumbers.parse(self.cleaned_data["number"]) | ||
| number = phonenumbers.format_number(number_obj, phonenumbers.PhoneNumberFormat.E164) | ||
| service = self.messaging_provider.get_messaging_service() | ||
| if not service.is_valid_number(number): | ||
| if self.messaging_provider.type == MessagingProviderType.meta_cloud_api: | ||
| phone_number_id = service.get_phone_number_id(number) | ||
| if not phone_number_id: | ||
| raise forms.ValidationError( | ||
| f"{number} was not found in the WhatsApp Business Account. " | ||
| "Please verify the number is registered with your business." | ||
| ) | ||
| self._phone_number_id = phone_number_id | ||
|
||
| elif not service.is_valid_number(number): | ||
SmittieC marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| self.warning_message = ( | ||
| f"{number} was not found at the provider. Please make sure it is there before proceeding" | ||
| ) | ||
| return number | ||
| except phonenumbers.NumberParseException: | ||
| raise forms.ValidationError("Enter a valid phone number (e.g. +12125552368).") from None | ||
|
|
||
| def post_save(self, channel: ExperimentChannel): | ||
| super().post_save(channel) | ||
| if hasattr(self, "_phone_number_id"): | ||
| channel.extra_data["phone_number_id"] = self._phone_number_id | ||
| channel.save(update_fields=["extra_data"]) | ||
|
|
||
|
|
||
| class SureAdhereChannelForm(WebhookUrlFormBase): | ||
| sureadhere_tenant_id = forms.CharField( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| import hashlib | ||
| import hmac | ||
|
|
||
| from django.http import HttpResponse, HttpResponseBadRequest | ||
|
|
||
| from apps.service_providers.models import MessagingProvider, MessagingProviderType | ||
|
|
||
|
|
||
| class MetaCloudAPIWebhook: | ||
| @staticmethod | ||
| def extract_message_values(data: dict) -> list[dict]: | ||
| """Extract value dicts that contain messages from Meta webhook payload.""" | ||
| values = [] | ||
| for entry in data.get("entry", []): | ||
| for change in entry.get("changes", []): | ||
| value = change.get("value", {}) | ||
| if "messages" in value and value.get("metadata", {}).get("phone_number_id"): | ||
| values.append(value) | ||
SmittieC marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return values | ||
|
|
||
| @staticmethod | ||
| 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.") | ||
|
|
||
| # verify_token is a server-side encrypted field, so we can't filter in the DB. | ||
| providers = MessagingProvider.objects.filter(type=MessagingProviderType.meta_cloud_api) | ||
|
|
||
| for provider in providers: | ||
| if provider.config.get("verify_token") == token: | ||
| return HttpResponse(challenge, content_type="text/plain") | ||
|
|
||
| return HttpResponseBadRequest("Verification failed.") | ||
|
|
||
| @staticmethod | ||
| 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[7:] | ||
| computed = hmac.new( | ||
| app_secret.encode(), | ||
| payload, | ||
| hashlib.sha256, | ||
| ).hexdigest() | ||
| return hmac.compare_digest(computed, expected_signature) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't this be moved into
MessagingService.is_valid_numberThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 4a5ca39. Added
is_valid_numbertoMetaCloudAPIServicewhich wraps the phone number ID lookup. The form'scleanmethod now callsservice.is_valid_number(number)uniformly for all providers — no provider-type check needed for validation.