Skip to content

Reference

Auto-generated from the source docstrings, grouped to mirror the R package's reference index.

Client

Create and configure the API client.

evolution_api.EvoClient

Bases: _BaseClient, MessagesMixin, NumbersMixin, InstanceMixin

Synchronous Evolution API v2 client.

Equivalent to the R package's evo_client(): holds base_url, api_key and instance, sets the apikey header and a custom User-Agent, and retries transient failures automatically.

Example

client = EvoClient( ... base_url="https://my-host", ... api_key="...", # or via the EVO_APIKEY env var ... instance="myInstance", ... ) client.send_text("5581999990000", "Hello from Python!") # doctest: +SKIP

Source code in src/evolution_api/client.py
class EvoClient(_BaseClient, MessagesMixin, NumbersMixin, InstanceMixin):
    """Synchronous Evolution API v2 client.

    Equivalent to the R package's ``evo_client()``: holds ``base_url``,
    ``api_key`` and ``instance``, sets the ``apikey`` header and a custom
    User-Agent, and retries transient failures automatically.

    Example:
        >>> client = EvoClient(
        ...     base_url="https://my-host",
        ...     api_key="...",          # or via the EVO_APIKEY env var
        ...     instance="myInstance",
        ... )
        >>> client.send_text("5581999990000", "Hello from Python!")  # doctest: +SKIP
    """

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        self._http = httpx.Client(headers=self._headers, timeout=self.timeout)

    def _execute(self, spec: RequestSpec, *, verbose: bool) -> Any:
        url = build_url(self.base_url, spec)
        if verbose:
            log_request(spec.method, spec.path, spec.body, self.timeout)

        status: int | None = None
        content_type: str = ""
        body: Any = None
        preview: str = ""
        t0 = time.perf_counter()
        for attempt in range(self.max_retries):
            try:
                response = self._http.request(spec.method, url, json=spec.body)
            except httpx.TransportError as exc:
                if should_retry(attempt, self.max_retries, None):
                    time.sleep(backoff_seconds(attempt))
                    continue
                raise EvolutionConnectionError(
                    f"Request to Evolution API failed (POST {spec.path}): {exc}"
                ) from exc
            status, content_type, body, preview = parse_response(response)
            if should_retry(attempt, self.max_retries, status):
                time.sleep(backoff_seconds(attempt))
                continue
            break
        elapsed = time.perf_counter() - t0

        assert status is not None
        if verbose:
            log_response(status, elapsed, content_type or "", preview or "")
        raise_for_status(status, spec.path, body)
        return body

    def close(self) -> None:
        """Close the underlying HTTP connection pool."""
        self._http.close()

    def __enter__(self) -> EvoClient:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        tb: TracebackType | None,
    ) -> None:
        self.close()

evolution_api.AsyncEvoClient

Bases: _BaseClient, MessagesMixin, NumbersMixin, InstanceMixin

Asynchronous Evolution API v2 client.

Exposes every method of :class:EvoClient, but each is awaitable::

async with AsyncEvoClient(base_url, api_key, instance) as client:
    await client.send_text("5581999990000", "Hello!")
Source code in src/evolution_api/client.py
class AsyncEvoClient(_BaseClient, MessagesMixin, NumbersMixin, InstanceMixin):
    """Asynchronous Evolution API v2 client.

    Exposes every method of :class:`EvoClient`, but each is awaitable::

        async with AsyncEvoClient(base_url, api_key, instance) as client:
            await client.send_text("5581999990000", "Hello!")
    """

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        super().__init__(*args, **kwargs)
        self._http = httpx.AsyncClient(headers=self._headers, timeout=self.timeout)

    async def _execute(self, spec: RequestSpec, *, verbose: bool) -> Any:
        url = build_url(self.base_url, spec)
        if verbose:
            log_request(spec.method, spec.path, spec.body, self.timeout)

        status: int | None = None
        content_type: str = ""
        body: Any = None
        preview: str = ""
        t0 = time.perf_counter()
        for attempt in range(self.max_retries):
            try:
                response = await self._http.request(spec.method, url, json=spec.body)
            except httpx.TransportError as exc:
                if should_retry(attempt, self.max_retries, None):
                    await asyncio.sleep(backoff_seconds(attempt))
                    continue
                raise EvolutionConnectionError(
                    f"Request to Evolution API failed (POST {spec.path}): {exc}"
                ) from exc
            status, content_type, body, preview = parse_response(response)
            if should_retry(attempt, self.max_retries, status):
                await asyncio.sleep(backoff_seconds(attempt))
                continue
            break
        elapsed = time.perf_counter() - t0

        assert status is not None
        if verbose:
            log_response(status, elapsed, content_type or "", preview or "")
        raise_for_status(status, spec.path, body)
        return body

    async def aclose(self) -> None:
        """Close the underlying async HTTP connection pool."""
        await self._http.aclose()

    async def __aenter__(self) -> AsyncEvoClient:
        return self

    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        tb: TracebackType | None,
    ) -> None:
        await self.aclose()

Send messages

Functions to send different types of WhatsApp messages. All are methods on EvoClient / AsyncEvoClient.

All send_* endpoints under message/<endpoint>/<instance>.

Source code in src/evolution_api/messages.py
class MessagesMixin:
    """All ``send_*`` endpoints under ``message/<endpoint>/<instance>``."""

    def send_text(
        self: ClientCore,
        number: str,
        text: str,
        *,
        delay: int | None = None,
        link_preview: bool | None = None,
        mentions_everyone: bool | None = None,
        mentioned: list[str] | None = None,
        quoted: dict[str, Any] | None = None,
        verbose: bool | None = None,
    ) -> Any:
        """Send a plain text WhatsApp message.

        Args:
            number: Recipient with country code (``"5581999990000"`` or ``"+55..."``).
            text: Message body.
            delay: Optional presence delay in milliseconds (simulates typing).
            link_preview: Enable URL link preview.
            mentions_everyone: Mention everyone in a group.
            mentioned: JIDs to mention (e.g. ``jid("+5581999990000")``).
            quoted: Baileys message ``{"key": ..., "message": ...}`` to reply to.
            verbose: Override the client's verbose setting for this call.
        """
        _assert_scalar_string(number, "number")
        _assert_scalar_string(text, "text")
        body = compact(
            {
                "number": number,
                "text": text,
                "delay": delay,
                "linkPreview": link_preview,
                "mentionsEveryOne": mentions_everyone,
                "mentioned": mentioned,
                "quoted": quoted,
            }
        )
        spec = RequestSpec("POST", evo_path("message", "sendText", self.instance), body)
        return self._execute(spec, verbose=self._resolve_verbose(verbose))

    def send_status(
        self: ClientCore,
        type: StatusType,
        content: str,
        *,
        caption: str | None = None,
        background_color: str | None = None,
        font: int | None = None,
        all_contacts: bool = False,
        status_jid_list: list[str] | None = None,
        verbose: bool | None = None,
    ) -> Any:
        """Post a WhatsApp status (story): text or media.

        Args:
            type: One of ``"text"``, ``"image"``, ``"video"``, ``"document"``, ``"audio"``.
            content: Text (for ``type="text"``) or URL/base64 for media.
            caption: Optional caption for media types.
            background_color: Hex colour for a text status (e.g. ``"#FF5733"``).
            font: Integer font id (0-14).
            all_contacts: If ``True``, send to all contacts.
            status_jid_list: Specific JIDs to receive the status.
        """
        if type not in ("text", "image", "video", "document", "audio"):
            raise EvolutionConfigError(
                'type must be one of "text", "image", "video", "document", "audio".'
            )
        _assert_scalar_string(content, "content")
        body = compact(
            {
                "type": type,
                "content": content,
                "caption": caption,
                "backgroundColor": background_color,
                "font": font,
                "allContacts": bool(all_contacts),
                "statusJidList": status_jid_list,
            }
        )
        spec = RequestSpec("POST", evo_path("message", "sendStatus", self.instance), body)
        return self._execute(spec, verbose=self._resolve_verbose(verbose))

    def send_media(
        self: ClientCore,
        number: str,
        mediatype: MediaType,
        mimetype: str,
        media: str,
        file_name: str,
        *,
        caption: str | None = None,
        delay: int | None = None,
        link_preview: bool | None = None,
        verbose: bool | None = None,
    ) -> Any:
        """Send an image, video, or document.

        ``media`` is flexible: an HTTP(S) URL, a local file path (auto-encoded to
        base64, ``~`` expanded), raw base64, or a ``data:*;base64,`` URI.

        Args:
            number: Recipient with country code.
            mediatype: One of ``"image"``, ``"video"``, ``"document"``.
            mimetype: e.g. ``"image/png"``, ``"video/mp4"``, ``"application/pdf"``.
            media: URL, file path, base64, or data-URI.
            file_name: Suggested filename for the recipient (match the MIME type).
            caption: Optional caption displayed with the media.
        """
        _assert_scalar_string(number, "number")
        _assert_scalar_string(mimetype, "mimetype")
        _assert_scalar_string(file_name, "file_name")
        if mediatype not in ("image", "video", "document"):
            raise EvolutionConfigError(
                f'mediatype must be one of "image", "video", or "document". Got {mediatype!r}.'
            )
        resolved = self._resolve_verbose(verbose)
        body = compact(
            {
                "number": number,
                "mediatype": mediatype,
                "mimetype": mimetype,
                "caption": caption,
                "media": normalize_media(media, verbose=resolved),
                "fileName": file_name,
                "delay": delay,
                "linkPreview": link_preview,
            }
        )
        spec = RequestSpec("POST", evo_path("message", "sendMedia", self.instance), body)
        return self._execute(spec, verbose=resolved)

    def send_whatsapp_audio(
        self: ClientCore,
        number: str,
        audio: str,
        *,
        delay: int | None = None,
        quoted: dict[str, Any] | None = None,
        verbose: bool | None = None,
    ) -> Any:
        """Send a voice note (push-to-talk).

        ``audio`` accepts a URL, base64, or a local file path (``~`` expanded,
        auto-encoded to base64).
        """
        _assert_scalar_string(number, "number")
        _assert_scalar_string(audio, "audio")
        resolved = self._resolve_verbose(verbose)
        body = compact(
            {
                "number": number,
                "audio": normalize_media(audio, verbose=resolved),
                "delay": delay,
                "quoted": quoted,
            }
        )
        spec = RequestSpec("POST", evo_path("message", "sendWhatsAppAudio", self.instance), body)
        return self._execute(spec, verbose=resolved)

    def send_sticker(
        self: ClientCore,
        number: str,
        sticker: str,
        *,
        delay: int | None = None,
        verbose: bool | None = None,
    ) -> Any:
        """Send a sticker.

        ``sticker`` accepts a URL, base64, or a local file path (``~`` expanded,
        auto-encoded to base64).
        """
        _assert_scalar_string(number, "number")
        _assert_scalar_string(sticker, "sticker")
        resolved = self._resolve_verbose(verbose)
        body = compact(
            {
                "number": number,
                "sticker": normalize_media(sticker, verbose=resolved),
                "delay": delay,
            }
        )
        spec = RequestSpec("POST", evo_path("message", "sendSticker", self.instance), body)
        return self._execute(spec, verbose=resolved)

    def send_location(
        self: ClientCore,
        number: str,
        latitude: float,
        longitude: float,
        *,
        name: str | None = None,
        address: str | None = None,
        verbose: bool | None = None,
    ) -> Any:
        """Send a geographic location pin."""
        _assert_scalar_string(number, "number")
        if not isinstance(latitude, (int, float)) or not isinstance(longitude, (int, float)):
            raise EvolutionConfigError("latitude and longitude must be numeric values.")
        body = compact(
            {
                "number": number,
                "latitude": latitude,
                "longitude": longitude,
                "name": name,
                "address": address,
            }
        )
        spec = RequestSpec("POST", evo_path("message", "sendLocation", self.instance), body)
        return self._execute(spec, verbose=self._resolve_verbose(verbose))

    def send_contact(
        self: ClientCore,
        number: str,
        contact: dict[str, Any] | list[dict[str, Any]],
        *,
        verbose: bool | None = None,
    ) -> Any:
        """Send one or more contacts (the ``wuid`` field is auto-generated).

        Args:
            number: Recipient with country code.
            contact: A single contact dict (``fullName``, ``phoneNumber``,
                ``organization``, ``email``, ``url``) or a list of such dicts.
                ``wuid`` is generated as ``<digits>@s.whatsapp.net`` when missing.
        """
        _assert_scalar_string(number, "number")
        contacts = [contact] if isinstance(contact, dict) else list(contact)

        normalized: list[dict[str, Any]] = []
        for ct in contacts:
            entry = dict(ct)
            if not entry.get("wuid"):
                phone = entry.get("phoneNumber") or number
                cleaned = "".join(c for c in str(phone) if c.isdigit())
                if cleaned:
                    entry["wuid"] = jid(cleaned)
            normalized.append({k: v for k, v in entry.items() if v is not None})

        body = compact({"number": number, "contact": normalized})
        spec = RequestSpec("POST", evo_path("message", "sendContact", self.instance), body)
        return self._execute(spec, verbose=self._resolve_verbose(verbose))

    def send_reaction(
        self: ClientCore,
        key: dict[str, Any],
        reaction: str,
        *,
        verbose: bool | None = None,
    ) -> Any:
        """React to a message with an emoji.

        Args:
            key: ``{"remoteJid": ..., "fromMe": ..., "id": ...}`` of the target.
            reaction: Emoji string; use ``""`` to remove an existing reaction.
        """
        if not isinstance(key, dict) or not key.get("id"):
            raise EvolutionConfigError('key must be a dict with at least an "id" field.')
        if not isinstance(reaction, str):
            raise EvolutionConfigError("reaction must be a string (emoji or empty string).")
        body = {"key": key, "reaction": reaction}
        spec = RequestSpec("POST", evo_path("message", "sendReaction", self.instance), body)
        return self._execute(spec, verbose=self._resolve_verbose(verbose))

    def send_buttons(
        self: ClientCore,
        number: str,
        title: str,
        description: str,
        footer: str,
        buttons: list[dict[str, Any]],
        *,
        delay: int | None = None,
        link_preview: bool | None = None,
        mentions_everyone: bool | None = None,
        verbose: bool | None = None,
    ) -> Any:
        """Send a message with interactive buttons.

        Warning:
            Interactive buttons are **not supported on the Baileys connector** and
            may be discontinued; this works on the Cloud API connector only. A
            :class:`UserWarning` is emitted. Consider :meth:`send_poll` instead.
        """
        warnings.warn(_BAILEYS_WARNING.format(kind="buttons"), UserWarning, stacklevel=2)
        _assert_scalar_string(number, "number")
        _assert_scalar_string(title, "title")
        _assert_scalar_string(description, "description")
        _assert_scalar_string(footer, "footer")
        if not isinstance(buttons, list) or len(buttons) == 0:
            raise EvolutionConfigError("buttons must be a non-empty list of button definitions.")
        body = compact(
            {
                "number": number,
                "title": title,
                "description": description,
                "footer": footer,
                "buttons": buttons,
                "delay": delay,
                "linkPreview": link_preview,
                "mentionsEveryOne": mentions_everyone,
            }
        )
        spec = RequestSpec("POST", evo_path("message", "sendButtons", self.instance), body)
        return self._execute(spec, verbose=self._resolve_verbose(verbose))

    def send_poll(
        self: ClientCore,
        number: str,
        name: str,
        values: list[str],
        *,
        selectable_count: int = 1,
        verbose: bool | None = None,
    ) -> Any:
        """Send a poll (question with selectable options).

        Args:
            number: Recipient with country code.
            name: Question text.
            values: Poll options (minimum 2).
            selectable_count: How many options a user may select (default 1).
        """
        _assert_scalar_string(number, "number")
        _assert_scalar_string(name, "name")
        if not isinstance(values, (list, tuple)) or len(values) < 2:
            raise EvolutionConfigError("values must be a list with at least 2 options.")
        body = {
            "number": number,
            "name": name,
            "values": list(values),
            "selectableCount": int(selectable_count),
        }
        spec = RequestSpec("POST", evo_path("message", "sendPoll", self.instance), body)
        return self._execute(spec, verbose=self._resolve_verbose(verbose))

    def send_list(
        self: ClientCore,
        number: str,
        title: str,
        description: str,
        button_text: str,
        sections: list[dict[str, Any]],
        *,
        footer: str = "",
        delay: int | None = None,
        verbose: bool | None = None,
    ) -> Any:
        """Send an interactive list message.

        Args:
            number: Recipient with country code.
            title: List message title.
            description: List body text.
            button_text: Text on the list button (e.g. ``"View options"``).
            sections: Section dicts, each ``{"title": ..., "rows": [...]}`` where a
                row is ``{"title": ..., "description"?: ..., "rowId"?: ...}``.
            footer: Footer text. Defaults to ``""`` (the API requires ``footerText``).

        Warning:
            Interactive lists are **not supported on the Baileys connector** and
            may be discontinued; this works on the Cloud API connector only. A
            :class:`UserWarning` is emitted. Consider :meth:`send_poll` instead.
        """
        warnings.warn(_BAILEYS_WARNING.format(kind="list messages"), UserWarning, stacklevel=2)
        _assert_scalar_string(number, "number")
        _assert_scalar_string(title, "title")
        _assert_scalar_string(description, "description")
        _assert_scalar_string(button_text, "button_text")
        if not isinstance(sections, list) or len(sections) == 0:
            raise EvolutionConfigError("sections must be a non-empty list of section definitions.")
        body = compact(
            {
                "number": number,
                "title": title,
                "description": description,
                "buttonText": button_text,
                "footerText": footer,
                "sections": sections,
                "delay": delay,
            }
        )
        spec = RequestSpec("POST", evo_path("message", "sendList", self.instance), body)
        return self._execute(spec, verbose=self._resolve_verbose(verbose))

send_text

send_text(number: str, text: str, *, delay: int | None = None, link_preview: bool | None = None, mentions_everyone: bool | None = None, mentioned: list[str] | None = None, quoted: dict[str, Any] | None = None, verbose: bool | None = None) -> Any

Send a plain text WhatsApp message.

Parameters:

Name Type Description Default
number str

Recipient with country code ("5581999990000" or "+55...").

required
text str

Message body.

required
delay int | None

Optional presence delay in milliseconds (simulates typing).

None
link_preview bool | None

Enable URL link preview.

None
mentions_everyone bool | None

Mention everyone in a group.

None
mentioned list[str] | None

JIDs to mention (e.g. jid("+5581999990000")).

None
quoted dict[str, Any] | None

Baileys message {"key": ..., "message": ...} to reply to.

None
verbose bool | None

Override the client's verbose setting for this call.

None
Source code in src/evolution_api/messages.py
def send_text(
    self: ClientCore,
    number: str,
    text: str,
    *,
    delay: int | None = None,
    link_preview: bool | None = None,
    mentions_everyone: bool | None = None,
    mentioned: list[str] | None = None,
    quoted: dict[str, Any] | None = None,
    verbose: bool | None = None,
) -> Any:
    """Send a plain text WhatsApp message.

    Args:
        number: Recipient with country code (``"5581999990000"`` or ``"+55..."``).
        text: Message body.
        delay: Optional presence delay in milliseconds (simulates typing).
        link_preview: Enable URL link preview.
        mentions_everyone: Mention everyone in a group.
        mentioned: JIDs to mention (e.g. ``jid("+5581999990000")``).
        quoted: Baileys message ``{"key": ..., "message": ...}`` to reply to.
        verbose: Override the client's verbose setting for this call.
    """
    _assert_scalar_string(number, "number")
    _assert_scalar_string(text, "text")
    body = compact(
        {
            "number": number,
            "text": text,
            "delay": delay,
            "linkPreview": link_preview,
            "mentionsEveryOne": mentions_everyone,
            "mentioned": mentioned,
            "quoted": quoted,
        }
    )
    spec = RequestSpec("POST", evo_path("message", "sendText", self.instance), body)
    return self._execute(spec, verbose=self._resolve_verbose(verbose))

send_status

send_status(type: StatusType, content: str, *, caption: str | None = None, background_color: str | None = None, font: int | None = None, all_contacts: bool = False, status_jid_list: list[str] | None = None, verbose: bool | None = None) -> Any

Post a WhatsApp status (story): text or media.

Parameters:

Name Type Description Default
type StatusType

One of "text", "image", "video", "document", "audio".

required
content str

Text (for type="text") or URL/base64 for media.

required
caption str | None

Optional caption for media types.

None
background_color str | None

Hex colour for a text status (e.g. "#FF5733").

None
font int | None

Integer font id (0-14).

None
all_contacts bool

If True, send to all contacts.

False
status_jid_list list[str] | None

Specific JIDs to receive the status.

None
Source code in src/evolution_api/messages.py
def send_status(
    self: ClientCore,
    type: StatusType,
    content: str,
    *,
    caption: str | None = None,
    background_color: str | None = None,
    font: int | None = None,
    all_contacts: bool = False,
    status_jid_list: list[str] | None = None,
    verbose: bool | None = None,
) -> Any:
    """Post a WhatsApp status (story): text or media.

    Args:
        type: One of ``"text"``, ``"image"``, ``"video"``, ``"document"``, ``"audio"``.
        content: Text (for ``type="text"``) or URL/base64 for media.
        caption: Optional caption for media types.
        background_color: Hex colour for a text status (e.g. ``"#FF5733"``).
        font: Integer font id (0-14).
        all_contacts: If ``True``, send to all contacts.
        status_jid_list: Specific JIDs to receive the status.
    """
    if type not in ("text", "image", "video", "document", "audio"):
        raise EvolutionConfigError(
            'type must be one of "text", "image", "video", "document", "audio".'
        )
    _assert_scalar_string(content, "content")
    body = compact(
        {
            "type": type,
            "content": content,
            "caption": caption,
            "backgroundColor": background_color,
            "font": font,
            "allContacts": bool(all_contacts),
            "statusJidList": status_jid_list,
        }
    )
    spec = RequestSpec("POST", evo_path("message", "sendStatus", self.instance), body)
    return self._execute(spec, verbose=self._resolve_verbose(verbose))

send_media

send_media(number: str, mediatype: MediaType, mimetype: str, media: str, file_name: str, *, caption: str | None = None, delay: int | None = None, link_preview: bool | None = None, verbose: bool | None = None) -> Any

Send an image, video, or document.

media is flexible: an HTTP(S) URL, a local file path (auto-encoded to base64, ~ expanded), raw base64, or a data:*;base64, URI.

Parameters:

Name Type Description Default
number str

Recipient with country code.

required
mediatype MediaType

One of "image", "video", "document".

required
mimetype str

e.g. "image/png", "video/mp4", "application/pdf".

required
media str

URL, file path, base64, or data-URI.

required
file_name str

Suggested filename for the recipient (match the MIME type).

required
caption str | None

Optional caption displayed with the media.

None
Source code in src/evolution_api/messages.py
def send_media(
    self: ClientCore,
    number: str,
    mediatype: MediaType,
    mimetype: str,
    media: str,
    file_name: str,
    *,
    caption: str | None = None,
    delay: int | None = None,
    link_preview: bool | None = None,
    verbose: bool | None = None,
) -> Any:
    """Send an image, video, or document.

    ``media`` is flexible: an HTTP(S) URL, a local file path (auto-encoded to
    base64, ``~`` expanded), raw base64, or a ``data:*;base64,`` URI.

    Args:
        number: Recipient with country code.
        mediatype: One of ``"image"``, ``"video"``, ``"document"``.
        mimetype: e.g. ``"image/png"``, ``"video/mp4"``, ``"application/pdf"``.
        media: URL, file path, base64, or data-URI.
        file_name: Suggested filename for the recipient (match the MIME type).
        caption: Optional caption displayed with the media.
    """
    _assert_scalar_string(number, "number")
    _assert_scalar_string(mimetype, "mimetype")
    _assert_scalar_string(file_name, "file_name")
    if mediatype not in ("image", "video", "document"):
        raise EvolutionConfigError(
            f'mediatype must be one of "image", "video", or "document". Got {mediatype!r}.'
        )
    resolved = self._resolve_verbose(verbose)
    body = compact(
        {
            "number": number,
            "mediatype": mediatype,
            "mimetype": mimetype,
            "caption": caption,
            "media": normalize_media(media, verbose=resolved),
            "fileName": file_name,
            "delay": delay,
            "linkPreview": link_preview,
        }
    )
    spec = RequestSpec("POST", evo_path("message", "sendMedia", self.instance), body)
    return self._execute(spec, verbose=resolved)

send_whatsapp_audio

send_whatsapp_audio(number: str, audio: str, *, delay: int | None = None, quoted: dict[str, Any] | None = None, verbose: bool | None = None) -> Any

Send a voice note (push-to-talk).

audio accepts a URL, base64, or a local file path (~ expanded, auto-encoded to base64).

Source code in src/evolution_api/messages.py
def send_whatsapp_audio(
    self: ClientCore,
    number: str,
    audio: str,
    *,
    delay: int | None = None,
    quoted: dict[str, Any] | None = None,
    verbose: bool | None = None,
) -> Any:
    """Send a voice note (push-to-talk).

    ``audio`` accepts a URL, base64, or a local file path (``~`` expanded,
    auto-encoded to base64).
    """
    _assert_scalar_string(number, "number")
    _assert_scalar_string(audio, "audio")
    resolved = self._resolve_verbose(verbose)
    body = compact(
        {
            "number": number,
            "audio": normalize_media(audio, verbose=resolved),
            "delay": delay,
            "quoted": quoted,
        }
    )
    spec = RequestSpec("POST", evo_path("message", "sendWhatsAppAudio", self.instance), body)
    return self._execute(spec, verbose=resolved)

send_sticker

send_sticker(number: str, sticker: str, *, delay: int | None = None, verbose: bool | None = None) -> Any

Send a sticker.

sticker accepts a URL, base64, or a local file path (~ expanded, auto-encoded to base64).

Source code in src/evolution_api/messages.py
def send_sticker(
    self: ClientCore,
    number: str,
    sticker: str,
    *,
    delay: int | None = None,
    verbose: bool | None = None,
) -> Any:
    """Send a sticker.

    ``sticker`` accepts a URL, base64, or a local file path (``~`` expanded,
    auto-encoded to base64).
    """
    _assert_scalar_string(number, "number")
    _assert_scalar_string(sticker, "sticker")
    resolved = self._resolve_verbose(verbose)
    body = compact(
        {
            "number": number,
            "sticker": normalize_media(sticker, verbose=resolved),
            "delay": delay,
        }
    )
    spec = RequestSpec("POST", evo_path("message", "sendSticker", self.instance), body)
    return self._execute(spec, verbose=resolved)

send_location

send_location(number: str, latitude: float, longitude: float, *, name: str | None = None, address: str | None = None, verbose: bool | None = None) -> Any

Send a geographic location pin.

Source code in src/evolution_api/messages.py
def send_location(
    self: ClientCore,
    number: str,
    latitude: float,
    longitude: float,
    *,
    name: str | None = None,
    address: str | None = None,
    verbose: bool | None = None,
) -> Any:
    """Send a geographic location pin."""
    _assert_scalar_string(number, "number")
    if not isinstance(latitude, (int, float)) or not isinstance(longitude, (int, float)):
        raise EvolutionConfigError("latitude and longitude must be numeric values.")
    body = compact(
        {
            "number": number,
            "latitude": latitude,
            "longitude": longitude,
            "name": name,
            "address": address,
        }
    )
    spec = RequestSpec("POST", evo_path("message", "sendLocation", self.instance), body)
    return self._execute(spec, verbose=self._resolve_verbose(verbose))

send_contact

send_contact(number: str, contact: dict[str, Any] | list[dict[str, Any]], *, verbose: bool | None = None) -> Any

Send one or more contacts (the wuid field is auto-generated).

Parameters:

Name Type Description Default
number str

Recipient with country code.

required
contact dict[str, Any] | list[dict[str, Any]]

A single contact dict (fullName, phoneNumber, organization, email, url) or a list of such dicts. wuid is generated as <digits>@s.whatsapp.net when missing.

required
Source code in src/evolution_api/messages.py
def send_contact(
    self: ClientCore,
    number: str,
    contact: dict[str, Any] | list[dict[str, Any]],
    *,
    verbose: bool | None = None,
) -> Any:
    """Send one or more contacts (the ``wuid`` field is auto-generated).

    Args:
        number: Recipient with country code.
        contact: A single contact dict (``fullName``, ``phoneNumber``,
            ``organization``, ``email``, ``url``) or a list of such dicts.
            ``wuid`` is generated as ``<digits>@s.whatsapp.net`` when missing.
    """
    _assert_scalar_string(number, "number")
    contacts = [contact] if isinstance(contact, dict) else list(contact)

    normalized: list[dict[str, Any]] = []
    for ct in contacts:
        entry = dict(ct)
        if not entry.get("wuid"):
            phone = entry.get("phoneNumber") or number
            cleaned = "".join(c for c in str(phone) if c.isdigit())
            if cleaned:
                entry["wuid"] = jid(cleaned)
        normalized.append({k: v for k, v in entry.items() if v is not None})

    body = compact({"number": number, "contact": normalized})
    spec = RequestSpec("POST", evo_path("message", "sendContact", self.instance), body)
    return self._execute(spec, verbose=self._resolve_verbose(verbose))

send_reaction

send_reaction(key: dict[str, Any], reaction: str, *, verbose: bool | None = None) -> Any

React to a message with an emoji.

Parameters:

Name Type Description Default
key dict[str, Any]

{"remoteJid": ..., "fromMe": ..., "id": ...} of the target.

required
reaction str

Emoji string; use "" to remove an existing reaction.

required
Source code in src/evolution_api/messages.py
def send_reaction(
    self: ClientCore,
    key: dict[str, Any],
    reaction: str,
    *,
    verbose: bool | None = None,
) -> Any:
    """React to a message with an emoji.

    Args:
        key: ``{"remoteJid": ..., "fromMe": ..., "id": ...}`` of the target.
        reaction: Emoji string; use ``""`` to remove an existing reaction.
    """
    if not isinstance(key, dict) or not key.get("id"):
        raise EvolutionConfigError('key must be a dict with at least an "id" field.')
    if not isinstance(reaction, str):
        raise EvolutionConfigError("reaction must be a string (emoji or empty string).")
    body = {"key": key, "reaction": reaction}
    spec = RequestSpec("POST", evo_path("message", "sendReaction", self.instance), body)
    return self._execute(spec, verbose=self._resolve_verbose(verbose))

send_buttons

send_buttons(number: str, title: str, description: str, footer: str, buttons: list[dict[str, Any]], *, delay: int | None = None, link_preview: bool | None = None, mentions_everyone: bool | None = None, verbose: bool | None = None) -> Any

Send a message with interactive buttons.

Warning

Interactive buttons are not supported on the Baileys connector and may be discontinued; this works on the Cloud API connector only. A :class:UserWarning is emitted. Consider :meth:send_poll instead.

Source code in src/evolution_api/messages.py
def send_buttons(
    self: ClientCore,
    number: str,
    title: str,
    description: str,
    footer: str,
    buttons: list[dict[str, Any]],
    *,
    delay: int | None = None,
    link_preview: bool | None = None,
    mentions_everyone: bool | None = None,
    verbose: bool | None = None,
) -> Any:
    """Send a message with interactive buttons.

    Warning:
        Interactive buttons are **not supported on the Baileys connector** and
        may be discontinued; this works on the Cloud API connector only. A
        :class:`UserWarning` is emitted. Consider :meth:`send_poll` instead.
    """
    warnings.warn(_BAILEYS_WARNING.format(kind="buttons"), UserWarning, stacklevel=2)
    _assert_scalar_string(number, "number")
    _assert_scalar_string(title, "title")
    _assert_scalar_string(description, "description")
    _assert_scalar_string(footer, "footer")
    if not isinstance(buttons, list) or len(buttons) == 0:
        raise EvolutionConfigError("buttons must be a non-empty list of button definitions.")
    body = compact(
        {
            "number": number,
            "title": title,
            "description": description,
            "footer": footer,
            "buttons": buttons,
            "delay": delay,
            "linkPreview": link_preview,
            "mentionsEveryOne": mentions_everyone,
        }
    )
    spec = RequestSpec("POST", evo_path("message", "sendButtons", self.instance), body)
    return self._execute(spec, verbose=self._resolve_verbose(verbose))

send_poll

send_poll(number: str, name: str, values: list[str], *, selectable_count: int = 1, verbose: bool | None = None) -> Any

Send a poll (question with selectable options).

Parameters:

Name Type Description Default
number str

Recipient with country code.

required
name str

Question text.

required
values list[str]

Poll options (minimum 2).

required
selectable_count int

How many options a user may select (default 1).

1
Source code in src/evolution_api/messages.py
def send_poll(
    self: ClientCore,
    number: str,
    name: str,
    values: list[str],
    *,
    selectable_count: int = 1,
    verbose: bool | None = None,
) -> Any:
    """Send a poll (question with selectable options).

    Args:
        number: Recipient with country code.
        name: Question text.
        values: Poll options (minimum 2).
        selectable_count: How many options a user may select (default 1).
    """
    _assert_scalar_string(number, "number")
    _assert_scalar_string(name, "name")
    if not isinstance(values, (list, tuple)) or len(values) < 2:
        raise EvolutionConfigError("values must be a list with at least 2 options.")
    body = {
        "number": number,
        "name": name,
        "values": list(values),
        "selectableCount": int(selectable_count),
    }
    spec = RequestSpec("POST", evo_path("message", "sendPoll", self.instance), body)
    return self._execute(spec, verbose=self._resolve_verbose(verbose))

send_list

send_list(number: str, title: str, description: str, button_text: str, sections: list[dict[str, Any]], *, footer: str = '', delay: int | None = None, verbose: bool | None = None) -> Any

Send an interactive list message.

Parameters:

Name Type Description Default
number str

Recipient with country code.

required
title str

List message title.

required
description str

List body text.

required
button_text str

Text on the list button (e.g. "View options").

required
sections list[dict[str, Any]]

Section dicts, each {"title": ..., "rows": [...]} where a row is {"title": ..., "description"?: ..., "rowId"?: ...}.

required
footer str

Footer text. Defaults to "" (the API requires footerText).

''
Warning

Interactive lists are not supported on the Baileys connector and may be discontinued; this works on the Cloud API connector only. A :class:UserWarning is emitted. Consider :meth:send_poll instead.

Source code in src/evolution_api/messages.py
def send_list(
    self: ClientCore,
    number: str,
    title: str,
    description: str,
    button_text: str,
    sections: list[dict[str, Any]],
    *,
    footer: str = "",
    delay: int | None = None,
    verbose: bool | None = None,
) -> Any:
    """Send an interactive list message.

    Args:
        number: Recipient with country code.
        title: List message title.
        description: List body text.
        button_text: Text on the list button (e.g. ``"View options"``).
        sections: Section dicts, each ``{"title": ..., "rows": [...]}`` where a
            row is ``{"title": ..., "description"?: ..., "rowId"?: ...}``.
        footer: Footer text. Defaults to ``""`` (the API requires ``footerText``).

    Warning:
        Interactive lists are **not supported on the Baileys connector** and
        may be discontinued; this works on the Cloud API connector only. A
        :class:`UserWarning` is emitted. Consider :meth:`send_poll` instead.
    """
    warnings.warn(_BAILEYS_WARNING.format(kind="list messages"), UserWarning, stacklevel=2)
    _assert_scalar_string(number, "number")
    _assert_scalar_string(title, "title")
    _assert_scalar_string(description, "description")
    _assert_scalar_string(button_text, "button_text")
    if not isinstance(sections, list) or len(sections) == 0:
        raise EvolutionConfigError("sections must be a non-empty list of section definitions.")
    body = compact(
        {
            "number": number,
            "title": title,
            "description": description,
            "buttonText": button_text,
            "footerText": footer,
            "sections": sections,
            "delay": delay,
        }
    )
    spec = RequestSpec("POST", evo_path("message", "sendList", self.instance), body)
    return self._execute(spec, verbose=self._resolve_verbose(verbose))

Chat utilities

Query and verify WhatsApp numbers, and build JIDs.

check_is_whatsapp / check_numbers — both hit chat/whatsappNumbers.

Source code in src/evolution_api/numbers.py
class NumbersMixin:
    """``check_is_whatsapp`` / ``check_numbers`` — both hit ``chat/whatsappNumbers``."""

    def check_is_whatsapp(
        self: ClientCore,
        numbers: list[str] | tuple[str, ...],
        *,
        verbose: bool | None = None,
    ) -> Any:
        """Verify whether phone numbers are registered on WhatsApp.

        Args:
            numbers: Phone numbers with country code (e.g. ``"5581999990000"``).
            verbose: Override the client's verbose setting for this call.

        Returns:
            The parsed API response indicating which numbers are registered.
        """
        if not isinstance(numbers, (list, tuple)) or len(numbers) == 0:
            raise EvolutionConfigError("numbers must be a non-empty list of phone numbers.")
        spec = RequestSpec(
            method="POST",
            path=evo_path("chat", "whatsappNumbers", self.instance),
            body={"numbers": list(numbers)},
        )
        return self._execute(spec, verbose=self._resolve_verbose(verbose))

    # Friendly alias (see DECISIONS.md D5).
    def check_numbers(
        self: ClientCore,
        numbers: list[str] | tuple[str, ...],
        *,
        verbose: bool | None = None,
    ) -> Any:
        """Alias for :meth:`check_is_whatsapp`."""
        return NumbersMixin.check_is_whatsapp(self, numbers, verbose=verbose)

check_is_whatsapp

check_is_whatsapp(numbers: list[str] | tuple[str, ...], *, verbose: bool | None = None) -> Any

Verify whether phone numbers are registered on WhatsApp.

Parameters:

Name Type Description Default
numbers list[str] | tuple[str, ...]

Phone numbers with country code (e.g. "5581999990000").

required
verbose bool | None

Override the client's verbose setting for this call.

None

Returns:

Type Description
Any

The parsed API response indicating which numbers are registered.

Source code in src/evolution_api/numbers.py
def check_is_whatsapp(
    self: ClientCore,
    numbers: list[str] | tuple[str, ...],
    *,
    verbose: bool | None = None,
) -> Any:
    """Verify whether phone numbers are registered on WhatsApp.

    Args:
        numbers: Phone numbers with country code (e.g. ``"5581999990000"``).
        verbose: Override the client's verbose setting for this call.

    Returns:
        The parsed API response indicating which numbers are registered.
    """
    if not isinstance(numbers, (list, tuple)) or len(numbers) == 0:
        raise EvolutionConfigError("numbers must be a non-empty list of phone numbers.")
    spec = RequestSpec(
        method="POST",
        path=evo_path("chat", "whatsappNumbers", self.instance),
        body={"numbers": list(numbers)},
    )
    return self._execute(spec, verbose=self._resolve_verbose(verbose))

check_numbers

check_numbers(numbers: list[str] | tuple[str, ...], *, verbose: bool | None = None) -> Any

Alias for :meth:check_is_whatsapp.

Source code in src/evolution_api/numbers.py
def check_numbers(
    self: ClientCore,
    numbers: list[str] | tuple[str, ...],
    *,
    verbose: bool | None = None,
) -> Any:
    """Alias for :meth:`check_is_whatsapp`."""
    return NumbersMixin.check_is_whatsapp(self, numbers, verbose=verbose)

Instance-controller endpoints. New vs. the R package (R is send-only).

Source code in src/evolution_api/instance.py
class InstanceMixin:
    """Instance-controller endpoints. New vs. the R package (R is send-only)."""

    def connection_state(self: ClientCore, *, verbose: bool | None = None) -> Any:
        """Return the channel connection state (health check).

        Calls ``GET /instance/connectionState/{instance}``. The response contains
        ``instance.instanceName`` and ``instance.state`` (e.g. ``"open"``,
        ``"connecting"``, ``"close"``).
        """
        spec = RequestSpec("GET", evo_path("instance", "connectionState", self.instance))
        return self._execute(spec, verbose=self._resolve_verbose(verbose))

connection_state

connection_state(*, verbose: bool | None = None) -> Any

Return the channel connection state (health check).

Calls GET /instance/connectionState/{instance}. The response contains instance.instanceName and instance.state (e.g. "open", "connecting", "close").

Source code in src/evolution_api/instance.py
def connection_state(self: ClientCore, *, verbose: bool | None = None) -> Any:
    """Return the channel connection state (health check).

    Calls ``GET /instance/connectionState/{instance}``. The response contains
    ``instance.instanceName`` and ``instance.state`` (e.g. ``"open"``,
    ``"connecting"``, ``"close"``).
    """
    spec = RequestSpec("GET", evo_path("instance", "connectionState", self.instance))
    return self._execute(spec, verbose=self._resolve_verbose(verbose))

evolution_api.jid

jid(number: str) -> str
jid(number: Iterable[str]) -> list[str]
jid(number: str | Iterable[str]) -> str | list[str]

Build a WhatsApp JID from a raw phone number.

Strips every non-digit character (including a leading +) and appends @s.whatsapp.net. Port of the R jid(); like the R version it is vectorized — pass an iterable to get a list back.

Examples:

>>> jid("+55 81 99999-0000")
'5581999990000@s.whatsapp.net'
>>> jid(["+5581999990000", "5511988887777"])
['5581999990000@s.whatsapp.net', '5511988887777@s.whatsapp.net']
Source code in src/evolution_api/numbers.py
def jid(number: str | Iterable[str]) -> str | list[str]:
    """Build a WhatsApp JID from a raw phone number.

    Strips every non-digit character (including a leading ``+``) and appends
    ``@s.whatsapp.net``. Port of the R ``jid()``; like the R version it is
    vectorized — pass an iterable to get a list back.

    Examples:
        >>> jid("+55 81 99999-0000")
        '5581999990000@s.whatsapp.net'
        >>> jid(["+5581999990000", "5511988887777"])
        ['5581999990000@s.whatsapp.net', '5511988887777@s.whatsapp.net']
    """
    if isinstance(number, str):
        return f"{_NON_DIGITS.sub('', number)}@s.whatsapp.net"
    if isinstance(number, Iterable):
        return [jid(n) for n in number]
    raise EvolutionConfigError("number must be a string or an iterable of strings.")

Receiving (webhooks)

evolution_api.webhooks.parse_webhook

parse_webhook(payload: dict[str, Any]) -> BaseEvent

Parse a webhook POST body into the most specific event model available.

Parameters:

Name Type Description Default
payload dict[str, Any]

The decoded JSON body of the webhook request.

required

Returns:

Name Type Description
A BaseEvent

class:MessagesUpsert, :class:ConnectionUpdate, :class:QrcodeUpdated,

BaseEvent

or — for any other event — a :class:GenericEvent. The concrete type can

BaseEvent

be matched on, and :attr:BaseEvent.event_type gives the normalized name.

Raises:

Type Description
EvolutionConfigError

If payload is not a dict or lacks an event key.

Source code in src/evolution_api/webhooks/parse.py
def parse_webhook(payload: dict[str, Any]) -> BaseEvent:
    """Parse a webhook POST body into the most specific event model available.

    Args:
        payload: The decoded JSON body of the webhook request.

    Returns:
        A :class:`MessagesUpsert`, :class:`ConnectionUpdate`, :class:`QrcodeUpdated`,
        or — for any other event — a :class:`GenericEvent`. The concrete type can
        be matched on, and :attr:`BaseEvent.event_type` gives the normalized name.

    Raises:
        EvolutionConfigError: If ``payload`` is not a dict or lacks an ``event`` key.
    """
    if not isinstance(payload, dict):
        raise EvolutionConfigError("webhook payload must be a dict (decoded JSON object).")
    event = payload.get("event")
    if not isinstance(event, str) or not event:
        raise EvolutionConfigError('webhook payload must contain a non-empty "event" string.')

    model = EVENT_MODELS.get(normalize_event_name(event), GenericEvent)
    return model.model_validate(payload)

evolution_api.webhooks.fastapi.webhook_router

webhook_router(handler: EventHandler, *, path: str = '/webhook') -> APIRouter

Build an APIRouter that receives Evolution webhooks and calls handler.

Mount it on an existing FastAPI app::

from fastapi import FastAPI
from evolution_api.webhooks import parse_webhook  # noqa: F401
from evolution_api.webhooks.fastapi import webhook_router

async def on_event(event):
    if event.event_type == "MESSAGES_UPSERT":
        print(event.data.key.remote_jid, event.data.message)

app = FastAPI()
app.include_router(webhook_router(on_event))

Parameters:

Name Type Description Default
handler EventHandler

Called with the parsed :class:BaseEvent. May be sync or async.

required
path str

Route path for the POST endpoint (default "/webhook").

'/webhook'

Returns:

Type Description
APIRouter

A FastAPI APIRouter ready to include_router.

Source code in src/evolution_api/webhooks/fastapi.py
def webhook_router(
    handler: EventHandler,
    *,
    path: str = "/webhook",
) -> APIRouter:
    """Build an ``APIRouter`` that receives Evolution webhooks and calls ``handler``.

    Mount it on an existing FastAPI app::

        from fastapi import FastAPI
        from evolution_api.webhooks import parse_webhook  # noqa: F401
        from evolution_api.webhooks.fastapi import webhook_router

        async def on_event(event):
            if event.event_type == "MESSAGES_UPSERT":
                print(event.data.key.remote_jid, event.data.message)

        app = FastAPI()
        app.include_router(webhook_router(on_event))

    Args:
        handler: Called with the parsed :class:`BaseEvent`. May be sync or async.
        path: Route path for the POST endpoint (default ``"/webhook"``).

    Returns:
        A FastAPI ``APIRouter`` ready to ``include_router``.
    """
    try:
        from fastapi import APIRouter
    except ModuleNotFoundError as exc:  # pragma: no cover - import guard
        raise ModuleNotFoundError(
            "FastAPI is required for webhook_router(). "
            'Install the extra: pip install "evolution-whatsapp[fastapi]".'
        ) from exc

    router = APIRouter()

    # `payload: dict` makes FastAPI read the raw JSON object as the request body,
    # which avoids a typed `Request` parameter (unresolvable here under
    # `from __future__ import annotations`).
    @router.post(path)
    async def _receive(payload: dict) -> dict[str, str]:  # type: ignore[type-arg]
        event = parse_webhook(payload)
        result = handler(event)
        if isawaitable(result):
            await result
        return {"status": "ok"}

    return router

evolution_api.webhooks.dataframe.as_dataframe

as_dataframe(events: Iterable[BaseEvent]) -> pd.DataFrame

Turn parsed webhook events into a pandas DataFrame.

Each event becomes a row. MESSAGES_UPSERT events are flattened into the useful columns (remote_jid, from_me, message_id, push_name, message_type, message_timestamp, text); other event types fill only the common columns.

Parameters:

Name Type Description Default
events Iterable[BaseEvent]

Any iterable of parsed events (e.g. from :func:parse_webhook).

required

Returns:

Type Description
DataFrame

A pandas.DataFrame, one row per event.

Source code in src/evolution_api/webhooks/dataframe.py
def as_dataframe(events: Iterable[BaseEvent]) -> pd.DataFrame:
    """Turn parsed webhook events into a pandas ``DataFrame``.

    Each event becomes a row. ``MESSAGES_UPSERT`` events are flattened into the
    useful columns (``remote_jid``, ``from_me``, ``message_id``, ``push_name``,
    ``message_type``, ``message_timestamp``, ``text``); other event types fill
    only the common columns.

    Args:
        events: Any iterable of parsed events (e.g. from :func:`parse_webhook`).

    Returns:
        A ``pandas.DataFrame``, one row per event.
    """
    try:
        import pandas as pd
    except ModuleNotFoundError as exc:  # pragma: no cover - import guard
        raise ModuleNotFoundError(
            "pandas is required for as_dataframe(). "
            'Install the extra: pip install "evolution-whatsapp[pandas]".'
        ) from exc

    return pd.DataFrame([_row(event) for event in events])

Event models

Pydantic v2 models for Evolution API v2 webhook events.

Evolution delivers events as an HTTP POST with a common envelope (event, instance, data and some metadata). Event names arrive dotted and lowercase in the payload (messages.upsert) but MESSAGES_UPSERT in configuration — both are accepted; :attr:BaseEvent.event_type normalizes to the UPPER_SNAKE form. Unknown events still parse as :class:GenericEvent so a handler never crashes on a new event type.

MessageKey

Bases: BaseModel

The Baileys message key identifying a message.

Source code in src/evolution_api/webhooks/models.py
class MessageKey(BaseModel):
    """The Baileys message key identifying a message."""

    model_config = _LENIENT

    remote_jid: str | None = Field(default=None, alias="remoteJid")
    from_me: bool | None = Field(default=None, alias="fromMe")
    id: str | None = None

MessageData

Bases: BaseModel

data payload of a MESSAGES_UPSERT event.

Source code in src/evolution_api/webhooks/models.py
class MessageData(BaseModel):
    """``data`` payload of a ``MESSAGES_UPSERT`` event."""

    model_config = _LENIENT

    key: MessageKey
    push_name: str | None = Field(default=None, alias="pushName")
    message: dict[str, Any] | None = None
    message_type: str | None = Field(default=None, alias="messageType")
    message_timestamp: int | None = Field(default=None, alias="messageTimestamp")

ConnectionData

Bases: BaseModel

data payload of a CONNECTION_UPDATE event.

Source code in src/evolution_api/webhooks/models.py
class ConnectionData(BaseModel):
    """``data`` payload of a ``CONNECTION_UPDATE`` event."""

    model_config = _LENIENT

    state: str | None = None
    status_reason: int | None = Field(default=None, alias="statusReason")

QrcodeData

Bases: BaseModel

data payload of a QRCODE_UPDATED event.

Source code in src/evolution_api/webhooks/models.py
class QrcodeData(BaseModel):
    """``data`` payload of a ``QRCODE_UPDATED`` event."""

    model_config = _LENIENT

    qrcode: dict[str, Any] | None = None

BaseEvent

Bases: BaseModel

Common webhook envelope shared by every event.

Source code in src/evolution_api/webhooks/models.py
class BaseEvent(BaseModel):
    """Common webhook envelope shared by every event."""

    model_config = _LENIENT

    event: str
    instance: str
    data: Any = None
    destination: str | None = None
    date_time: str | None = None
    sender: str | None = None
    server_url: str | None = None
    apikey: str | None = None

    @property
    def event_type(self) -> str:
        """The event name normalized to ``UPPER_SNAKE`` (e.g. ``MESSAGES_UPSERT``)."""
        return normalize_event_name(self.event)
event_type property
event_type: str

The event name normalized to UPPER_SNAKE (e.g. MESSAGES_UPSERT).

MessagesUpsert

Bases: BaseEvent

A received-message event (MESSAGES_UPSERT).

Source code in src/evolution_api/webhooks/models.py
class MessagesUpsert(BaseEvent):
    """A received-message event (``MESSAGES_UPSERT``)."""

    data: MessageData

ConnectionUpdate

Bases: BaseEvent

A connection-state change (CONNECTION_UPDATE).

Source code in src/evolution_api/webhooks/models.py
class ConnectionUpdate(BaseEvent):
    """A connection-state change (``CONNECTION_UPDATE``)."""

    data: ConnectionData

QrcodeUpdated

Bases: BaseEvent

A new QR code to scan (QRCODE_UPDATED).

Source code in src/evolution_api/webhooks/models.py
class QrcodeUpdated(BaseEvent):
    """A new QR code to scan (``QRCODE_UPDATED``)."""

    data: QrcodeData

GenericEvent

Bases: BaseEvent

Any event without a dedicated typed model; data is left untyped.

Source code in src/evolution_api/webhooks/models.py
class GenericEvent(BaseEvent):
    """Any event without a dedicated typed model; ``data`` is left untyped."""

normalize_event_name

normalize_event_name(event: str) -> str

"messages.upsert" / "MESSAGES_UPSERT" -> "MESSAGES_UPSERT".

Source code in src/evolution_api/webhooks/models.py
def normalize_event_name(event: str) -> str:
    """``"messages.upsert"`` / ``"MESSAGES_UPSERT"`` -> ``"MESSAGES_UPSERT"``."""
    return event.replace(".", "_").replace("-", "_").upper()

Errors

evolution_api.EvolutionError

Bases: Exception

Base class for every error raised by this package.

Source code in src/evolution_api/exceptions.py
class EvolutionError(Exception):
    """Base class for every error raised by this package."""

evolution_api.EvolutionConfigError

Bases: EvolutionError, ValueError

Invalid client configuration or argument.

Raised up front by the client constructor and send_* validation, mirroring the R package's .assert_scalar_string() / cli_abort checks.

Source code in src/evolution_api/exceptions.py
class EvolutionConfigError(EvolutionError, ValueError):
    """Invalid client configuration or argument.

    Raised up front by the client constructor and ``send_*`` validation,
    mirroring the R package's ``.assert_scalar_string()`` / ``cli_abort`` checks.
    """

evolution_api.EvolutionConnectionError

Bases: EvolutionError

A network/connection error occurred before an HTTP response was received.

Source code in src/evolution_api/exceptions.py
class EvolutionConnectionError(EvolutionError):
    """A network/connection error occurred before an HTTP response was received."""

evolution_api.EvolutionAPIError

Bases: EvolutionError

The Evolution API returned an HTTP error status (>= 400).

Attributes:

Name Type Description
status

The HTTP status code.

api_message

The message extracted from the response body (response.message or message), or a fallback string.

body

The parsed response body (dict) or the raw text.

path

The request path that failed, e.g. message/sendText/myInstance.

Source code in src/evolution_api/exceptions.py
class EvolutionAPIError(EvolutionError):
    """The Evolution API returned an HTTP error status (>= 400).

    Attributes:
        status: The HTTP status code.
        api_message: The message extracted from the response body
            (``response.message`` or ``message``), or a fallback string.
        body: The parsed response body (``dict``) or the raw text.
        path: The request path that failed, e.g. ``message/sendText/myInstance``.
    """

    def __init__(
        self,
        status: int,
        api_message: str,
        *,
        body: Any = None,
        path: str | None = None,
    ) -> None:
        self.status = status
        self.api_message = api_message
        self.body = body
        self.path = path
        endpoint = f" (POST {path})" if path else ""
        super().__init__(f"Evolution API returned HTTP {status}{endpoint}: {api_message}")