Skip to content

Releases: david-lev/pywa

3.5.0

Choose a tag to compare

@david-lev david-lev released this 08 Nov 18:35
9335df8

What's Changed

Update with pip: pip3 install -U pywa

  • [client] adding send_voice and wait_until_played listener
  • [listeners] adding wait_for_location when sending location request
  • [sent] adding SentMediaMessage with uploaded_media attr for sent messages
  • [message] adding unsupported attr
  • [message] adding support for copy voice message
  • [base_update] add identity_key_hash parameter to message methods for identity verification
  • [templates] adding text_extraction_for_headline and text_extraction_for_tap_target to CreativeFeaturesSpec
  • [flows] fixing index in FlowCompletion.get_media
from pywa import WhatsApp, types

wa = WhatsApp(...)

@wa.on_message(filters=filters.command("start"))
def on_start_message(_: WhatsApp, m: types.Message):
    # Send a welcome voice message
    sent = m.reply_voice(
        voice=text_to_speech("Welcome to our service! How can we assist you today?")
    )
    sent.wait_until_played()  # Wait until the voice message is played
    print(sent.uploaded_media)  # Access uploaded media

    location = m.reply_location_request(
        "Please share your location to find nearby services."
    ).wait_for_location().location # Wait for user to share location
    print(f"User shared location: {location.latitude}, {location.longitude}")


@wa.on_message(filters=filters.unsupported)
def on_unsupported_message(_: WhatsApp, m: types.Message):
    m.reply_text(
        # Now you can access the unsupported message type
        f"Sorry, we cannot process messages of type '{m.unsupported.type}' yet."
    )

Full Changelog: 3.4.0...3.5.0

3.4.0

Choose a tag to compare

@david-lev david-lev released this 26 Oct 20:11
6b06fa5

What's Changed

Update with pip: pip3 install -U pywa

  • [templates] adding support for HeaderGIF
  • [templates] override mimetype when sending template with media
  • [helpers] fix closing file

Full Changelog: 3.3.0...3.4.0

3.3.0

Choose a tag to compare

@david-lev david-lev released this 25 Oct 21:32
6fc4849

What's Changed

Update with pip: pip3 install -U pywa

  • [templates] allowing to override header example mime-type
  • [helpers] stream template examples when possible (sync/async)
  • [helpers] fix template example resolving for Media type

Full Changelog: 3.2.0...3.3.0

3.2.0

Choose a tag to compare

@david-lev david-lev released this 24 Oct 08:39
1b75e13

What's Changed

Update with pip: pip3 install -U pywa

  • [media] adding uploaded_by, uploaded_at and more helpers to Media type
  • [client] implementing Identity Change Check
  • [client] adding get_media_bytes and stream_media
  • [client] adding support for voice messages
  • [client] updating update_business_phone_number_settings method to take specific settings
  • [base_update] adding method to handle the update again
  • [message] fix typing of buttons for copy and remove 'copy' system message
    • [templates] adding image_background_gen option to CreativeFeaturesSpec
  • [sent_update] moving the input from user to the SentMessage level
  • [helpers] stream downloads and uploads
  • [utils] bump GRAPH_API version to 24.0
from pywa import WhatsApp, types, filters

wa = WhatsApp(...)

@wa.on_message(filters.image)
def on_image(_: WhatsApp, m: types.Message):
    if not m.image.is_expired:
        save_to_db(m.image.id, m.image.expires_at)

    # Access image metadata
    print(m.image.days_until_expiration)
    print(m.image.uploaded_at, m.image.uploaded_by, m.image.uploaded_to)

    # Reupload the image to extend its expiration
    new_media = m.image.reupload()

    # Stream the media to your storage
    with httpx.Client() as client:
        client.post(url="https://your-storage-service/upload", content=m.image.stream())
    
    # We also stream uploads!
    wa.upload_media("https://your-storage-service/download/12345")
from pywa import WhatsApp, types, filters, errors

wa = WhatsApp(...)

# Enable Identity Key Check
wa.update_business_phone_number_settings(
    user_identity_change=types.UserIdentityChangeSettings(
        enable_identity_key_check=True
    )
)

# Save identity_key_hash to database for future reference
@wa.on_message(my_filter_for_new_user)
def on_new_user(_: WhatsApp, m: types.Message):
    identity_key_hash = m.from_user.identity_key_hash

# Use the saved identity_key_hash from database to ensure message deliver
wa.send_message(
    to=...,
    text=...,
    identity_key_hash=...,
)

# Monitor for identity changes
@wa.on_identity_change
def on_identity_change(_: WhatsApp, identity_change: types.IdentityChange):
    print(identity_change)

# Monitor for identity key mismatches
@wa.on_message_status(filters.failed_with(errors.RecipientIdentityKeyMismatch))
def on_identity_key_mismatch(_: WhatsApp, status: types.MessageStatus):
    status.reply(
        text="We noticed that your WhatsApp identity has changed. Please verify your identity to continue receiving messages from us."
    )
from pywa import WhatsApp

wa.send_audio(
    to=...,
    audio=...,
    is_voice=True, # Send as voice note
)

Full Changelog: 3.1.1...3.2.0

3.1.1

Choose a tag to compare

@yehuda-lev yehuda-lev released this 21 Sep 12:40
d3ca66c

What's Changed

Update with pip: pip3 install -U pywa

  • [server] Fix filter updates

Full Changelog: 3.1.0...3.1.1

3.1.0

Choose a tag to compare

@david-lev david-lev released this 20 Sep 21:52
0b758dc

What's Changed

Update with pip: pip3 install -U pywa

  • [types] adding RawUpdate type
  • [flows] update enabled and required fields to use Condition type
  • [flows] added support for DataExchangeAction in DataSource (on_select_action and on_unselect_action)
  • [flows] update FLOW_JSON version to 7.3
  • [client] refactoring media resolving
  • [templates] adding filename when sending document
  • [media] allow to re-upload media to another phone id
from pywa import WhatsApp, types

wa = WhatsApp(...)

@wa.on_raw_update
def on_raw(_: WhatsApp, raw: types.RawUpdate):
    print(raw["entry"])  # the raw dict
    print(raw.id, raw.field, raw.value)  # shortcut properties
    print(raw.raw)  # the raw bytes of the updat
flow = FlowJSON(
    screens=[
        Screen(
            layout=Layout(
                children=[
                    TextHeading(text="Welcome to Pywa!"),
                    TextSubheading(text="Hi there, I am using Pywa."),
                    display_txt := TextInput(
                        name="display_txt", label="Type 'something'"
                    ),
                    TextBody(
                        text="You typed 'something'!",
                        visible=display_txt.ref == "something",
                    ),  # Example of conditional visibility
                ]
            )
        )
    ]
)

Full Changelog: 3.0.0...3.1.0

3.0.0

Choose a tag to compare

@david-lev david-lev released this 22 Aug 13:57
d099323

PyWa v3.0.0 is here!

Update with pip: pip3 install -U pywa

This is one of the biggest updates to PyWa ever, bringing calls, user preferences, a fully redesigned template system, new update types, and more.

Important

Migration to v3 introduces breaking changes. Please review carefully before upgrading.

✨ Highlights

  • Templates: completely redesigned — more flexible, reusable, and powerful.
  • Calls: full support for making, receiving, and managing calls.
  • User Preferences: listen for marketing opt-in/out events.
  • System Updates: PhoneNumberChange and IdentityChange now first-class updates.
  • Listeners: can now handle all update types, not just user messages.
  • Client Improvements:
    • New methods: delete_media, update_display_name, get_business_account, and more.
    • upload_media now returns a Media object.
    • send_* methods enforce keyword-only context arguments.
  • Types: SuccessResult replaces bool for extensibility.
  • Security: XSS vulnerability fixed.
  • Docs: new PyWa logo! 🚀 (by @nyatitilkesh)

🔥 Breaking changes

  • Old template system removed → use the new API.
  • system messages removed from Message → use PhoneNumberChange / IdentityChange.
  • upload_media returns Media instead of a string.
  • send_* methods require keyword-only context args.
  • Methods like mark_as_read, indicate_typing now return SuccessResult.
  • FlowRequestDecryptedMedia replaces tuple return.

See full details and migration steps in the migration guide.

❤️ Thanks

  • A huge thanks to my wife for always providing endless support and patience.
  • Special thanks to my brother @yehuda-lev for his help, code reviews, testing, and for supporting users in the community groups 🙌.

📚 Resources

👏 New Contributors

💡 Examples

  • Create and send template messages

See Templates for more details and examples.

from pywa import WhatsApp, types
from pywa.types.templates import *

wa = WhatsApp(..., business_account_id=123456)

# Create a template
wa.create_template(
    template=Template(
        name="buy_new_iphone_x",
        category=TemplateCategory.MARKETING,
        language=TemplateLanguage.ENGLISH_US,
        parameter_format=ParamFormat.NAMED,
        components=[
            ht := HeaderText("The New iPhone {{iphone_num}} is here!", iphone_num=15),
            bt := BodyText("Buy now and use the code {{code}} to get {{per}}% off!", code="WA_IPHONE_15", per=15),
            FooterText(text="Powered by PyWa"),
            Buttons(
                buttons=[
                    url := URLButton(text="Buy Now", url="https://example.com/shop/{{1}}", example="iphone15"),
                    PhoneNumberButton(text="Call Us", phone_number="1234567890"),
                    qrb1 := QuickReplyButton(text="Unsubscribe from marketing messages"),
                    qrb2 := QuickReplyButton(text="Unsubscribe from all messages"),
                ]
            ),

        ]
    ),
)

# Send the template message
wa.send_template(
    to="9876543210",
    name="buy_new_iphone_x",
    language=TemplateLanguage.ENGLISH_US,
    params=[
        ht.params(iphone_num=30),
        bt.params(code="WA_IPHONE_30", per=30),
        url.params(url_variable="iphone30", index=0),
        qrb1.params(callback_data="unsubscribe_from_marketing_messages", index=1),
        qrb2.params(callback_data="unsubscribe_from_all_messages", index=2),
    ]
)

# Manage templates
templates = wa.get_templates(statuses=[TemplateStatus.APPROVED])
print(templates.total_count, templates.message_template_limit)
for template in templates:
    print(template)
    template.update(...)
    template.delete()
    template.duplicate(...)
    template.compare(...)
    template.send(to=...)

# Handle changes
@wa.on_template_status_update
def on_template_status_update(_: WhatsApp, status: types.TemplateStatusUpdate):
    print("Template status update:", status.template_name, status.template_language, status.new_status)
    
@wa.on_template_category_update
def on_template_category_update(_: WhatsApp, category: types.TemplateCategoryUpdate):
    print("Template category update:", category.template_name, category.previous_category, category.new_category)
    
@wa.on_template_quality_update
def on_template_quality_update(_: WhatsApp, quality: types.TemplateQualityUpdate):
    print("Template quality update:", quality.template_name, quality.previous_quality_score, quality.new_quality_score)
    
@wa.on_template_components_update
def on_template_components_update(_: WhatsApp, components: types.TemplateComponentsUpdate):
    print("Template components update:", components.template_name, components.template_title, components.template_element)

# Inline listeners
wa.create_template(template=Template(...)).wait_until_approved()
  • Listen to phone number change and identity change
from pywa import WhatsApp, types

wa = WhatsApp(...)

@wa.on_phone_number_change
def on_phone_number_change(_: WhatsApp, update: types.PhoneNumberChange):
    repository.update_phone_number(old=update.old_wa_id, new=update.new_wa_id) # update user wa_id in your db

@wa.on_identity_change
def on_identity_change(_: WhatsApp, update: types.IdentityChange):
    repository.log_out_user(wa_id=update.sender)  # secure the user account
  • Calling
from pywa import WhatsApp, types, filters

@wa.on_call_connect(filters.incoming_call)
def on_incoming_call(_: WhatsApp, call: types.CallConnect):
    print(f"Incoming call from {call.from_user.name}: {call.session}")
    call.pre_accept(...)
    call.accept(...)
    call.terminate(...)
    call.reject(...)

@wa.on_call_terminate
def on_call_terminate(_: WhatsApp, call: types.CallTerminate):
    print("Call terminated:", call.duration, call.start_time, call.end_time, call.status)
    
@wa.on_call_status
def on_call_status(_: WhatsApp, call: types.CallStatus):
    print("Call status update:", call.status)

@wa.on_call_permission_update
def on_call_permission_update(_: WhatsApp, perm: types.CallPermissionUpdate):
    print("Call permission update:", perm.response, perm.response_source, perm.expiration_timestamp)
    if not perm.is_expired:
        perm.call(...)
  • User marketing preferences
from pywa import WhatsApp, types

@wa.on_user_marketing_preferences
def on_user_marketing_preferences(_: WhatsApp, prefs: types.UserMarketingPreferences):
    print(prefs.detail)
    if prefs:
        repository.opt_in(prefs.sender)
    else:
        repository.opt_out(prefs.sender)

Full Changelog: 2.11.0...3.0.0

3.0.0-rc.3

3.0.0-rc.3 Pre-release
Pre-release

Choose a tag to compare

@david-lev david-lev released this 06 Aug 13:32
c8475fe

What's Changed

Update with pip: pip3 install -U pywa==3.0.0-rc.3

Caution

Make sure to read the migration guide before updating to this version!

Important

Please let us know in the Telegram group or the GitHub discussion if you encountered any problems during the migration

Note

Updated documentation to the new version is available here

  • [templates] params is now can be called on class level
  • [templates] adding support for library_input when creating library templates
  • [templates] adding support for degrees_of_freedom_spec when creating template
  • [listeners] handling old to parameter in listen and update migration guide

Full Changelog: 3.0.0-rc.2...3.0.0-rc.3

3.0.0-rc.2

3.0.0-rc.2 Pre-release
Pre-release

Choose a tag to compare

@david-lev david-lev released this 04 Aug 10:24
18dc6dc

What's Changed

Update with pip: pip3 install -U pywa==3.0.0-rc.2

Caution

Make sure to read the migration guide before updating to this version!

Important

Please let us know in the Telegram group or the GitHub discussion if you encountered any problems during the migration

Note

Updated documentation to the new version is available here

  • [client] allowing to use mm-lite-api when sending a template
  • [templates] allowing to set app-depplinks in URLButton's
  • [templates] adding TopBlockReasonType enum
  • [client] adding get_business_account method
  • [client] adding deregister_phone_number method
  • [client] allowing to get and set StorageConfiguration
  • [callback] adding is_quick_reply to CallbackButton
  • [callback] validate not kw_only in dataclasses
  • [client] fix creating LibraryTemplate
  • [system] support old customer_changed_number sys type
  • [docs] new logo for pywa!

Full Changelog: 3.0.0-rc.1...3.0.0-rc.2

3.0.0-rc.1

3.0.0-rc.1 Pre-release
Pre-release

Choose a tag to compare

@david-lev david-lev released this 31 Jul 07:12
d7f6ff3

What's Changed

Update with pip: pip3 install -U pywa==3.0.0-rc.1

Caution

Make sure to read the migration guide before updating to this version!

Important

Please let us know in the Telegram group or the GitHub discussion if you encountered any problems during the migration

Note

Updated documentation to the new version is available here

  • [templates] refactored and improved templates support
  • [calls] added full support for calls
  • [user_preferences] added full support for user preferences
  • [server] continued handling if listener is not using the update
  • [system] moved system messages to PhoneNumberChange and IdentityChange updates
  • [client] forced keyword-only for context args in send_message, send_image, and other send_... methods
  • [types] returned SuccessResult instead of bool to allow future extension with other attributes
  • [client] upload_media returns Media object
  • [client] added get_business_phone_number_settings and update_business_phone_number_settings to get and update calling settings
  • [client] added update_display_name method to update the WhatsApp display name
  • [security] fixed XSS vulnerability
  • [api] suggest to provide custom httpx.Client on httpx.RequestError
  • [handlers] added on_completion decorator to flow request callback wrapper
  • [errors] show more descriptive error messages
  • [base_update] added waba_id for all user updates
  • [message] added referral field
  • [types] support is_on_biz_app in BusinessPhoneNumber
  • [client] added delete_media method
  • [listeners] check if server exists before starting to listen
  • [utils] handled enum values case-sensitively
  • [utils] returned FlowRequestDecryptedMedia instead of (media_id, filename, data) tuple
  • [utils] new APIObject to get fields from datacls
  • [deprecations] removed attrs and types marked as deprecated

New Contributors

Full Changelog: 2.11.0...3.0.0-rc.1