From 2acb57d9d0e54e44b0e2a8102a99a766c5d44f4d Mon Sep 17 00:00:00 2001 From: Nicolas Asanov Date: Tue, 20 Oct 2026 17:10:15 -0400 Subject: [PATCH 1/2] feat(VAPI-3321): add attestation_level and identity to Transfer BXML verb --- bandwidth/models/bxml/verbs/transfer.py | 10 ++++++++-- test/unit/models/bxml/test_transfer.py | 20 +++++++++++++++++--- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/bandwidth/models/bxml/verbs/transfer.py b/bandwidth/models/bxml/verbs/transfer.py index d5de1997..0cdc40d6 100644 --- a/bandwidth/models/bxml/verbs/transfer.py +++ b/bandwidth/models/bxml/verbs/transfer.py @@ -23,7 +23,7 @@ def __init__( password: str=None, fallback_username: str=None, fallback_password: str=None, tag: str=None, diversion_treatment: str=None, diversion_reason: str=None, - privacy: bool=None + privacy: bool=None, attestation_level: str=None, identity: str=None ): """Initialize a verb @@ -63,6 +63,8 @@ def __init__( This parameter is considered only when diversionTreatment is set to stack. Defaults is unknown. Defaults to None. privacy (bool, optional): A boolean value to indicate whether the calling number should be hidden. Use transfer_caller_display_name to customize the displayed name. Default is false. + attestation_level (str, optional): The STIR/SHAKEN attestation level Bandwidth signs the transferred call at. Can be A, B, or C. US outbound only. Applies only to accounts using the Hosted Signing Service, otherwise ignored. If omitted, the account-level attestation policy applies. Cannot be combined with identity. Defaults to None. + identity (str, optional): A SIP Identity header value (PASSporT) signed by the customer, passed unmodified on the transferred call. Bandwidth does not sign the call or verify the signature. Max length 2000 characters. Cannot be combined with attestation_level. Defaults to None. """ self.transfer_to = transfer_to self.transfer_caller_id = transfer_caller_id @@ -80,6 +82,8 @@ def __init__( self.diversion_treatment = diversion_treatment self.diversion_reason = diversion_reason self.privacy = privacy + self.attestation_level = attestation_level + self.identity = identity super().__init__( tag="Transfer", nested_verbs=self.transfer_to @@ -102,7 +106,9 @@ def _attributes(self): "fallbackPassword": self.fallback_password, "tag": self.tag, "diversionTreatment": self.diversion_treatment, - "diversionReason": self.diversion_reason + "diversionReason": self.diversion_reason, + "attestationLevel": self.attestation_level, + "identity": self.identity } def add_transfer_recipient(self, recipient: Union[PhoneNumber, SipUri]): diff --git a/test/unit/models/bxml/test_transfer.py b/test/unit/models/bxml/test_transfer.py index 09f6fbaf..de08ff33 100644 --- a/test/unit/models/bxml/test_transfer.py +++ b/test/unit/models/bxml/test_transfer.py @@ -37,7 +37,8 @@ def setUp(self): fallback_username="fallbackUser", fallback_password="fallbackPass", diversion_treatment="propagate", - diversion_reason="away" + diversion_reason="away", + attestation_level="A" ) def test_instance(self): @@ -46,10 +47,23 @@ def test_instance(self): assert isinstance(self.transfer, Verb) def test_to_bxml(self): - expected = 'sip@bw.com' + expected = 'sip@bw.com' assert expected == self.transfer.to_bxml() def test_add_verb(self): - expected = 'sip@bw.com+19195551234' + expected = 'sip@bw.com+19195551234' self.transfer.add_transfer_recipient(self.phone_number) assert expected == self.transfer.to_bxml() + + def test_identity_to_bxml(self): + transfer = Transfer( + transfer_to=[self.sip_uri], + identity="eyJhbGciOiJFUzI1NiJ9.e30.c2ln;info=;ppt=\"shaken\"" + ) + expected = 'sip@bw.com' + assert expected == transfer.to_bxml() + + def test_attestation_level_identity_omitted_by_default(self): + transfer = Transfer(transfer_to=[self.sip_uri]) + assert 'attestationLevel' not in transfer.to_bxml() + assert 'identity' not in transfer.to_bxml() From 6d670e58df0127976f56dd6fafc626920fcb55a2 Mon Sep 17 00:00:00 2001 From: Nicolas Asanov Date: Tue, 3 Nov 2026 12:10:39 -0500 Subject: [PATCH 2/2] VAPI-4422 Tighten Attestation Level and Identity Docs --- bandwidth/models/bxml/verbs/transfer.py | 4 ++-- test/unit/models/bxml/test_transfer.py | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/bandwidth/models/bxml/verbs/transfer.py b/bandwidth/models/bxml/verbs/transfer.py index 0cdc40d6..37832dbe 100644 --- a/bandwidth/models/bxml/verbs/transfer.py +++ b/bandwidth/models/bxml/verbs/transfer.py @@ -63,8 +63,8 @@ def __init__( This parameter is considered only when diversionTreatment is set to stack. Defaults is unknown. Defaults to None. privacy (bool, optional): A boolean value to indicate whether the calling number should be hidden. Use transfer_caller_display_name to customize the displayed name. Default is false. - attestation_level (str, optional): The STIR/SHAKEN attestation level Bandwidth signs the transferred call at. Can be A, B, or C. US outbound only. Applies only to accounts using the Hosted Signing Service, otherwise ignored. If omitted, the account-level attestation policy applies. Cannot be combined with identity. Defaults to None. - identity (str, optional): A SIP Identity header value (PASSporT) signed by the customer, passed unmodified on the transferred call. Bandwidth does not sign the call or verify the signature. Max length 2000 characters. Cannot be combined with attestation_level. Defaults to None. + attestation_level (str, optional): The STIR/SHAKEN attestation level to sign the transferred call with. Can be A, B, or C. Only applies to accounts using the Hosted Signing Service. Cannot be combined with identity. Defaults to None. + identity (str, optional): A SIP Identity header value (PASSporT) signed by you, sent unmodified on the transferred call. Max length 2000 characters. Cannot be combined with attestation_level. Defaults to None. """ self.transfer_to = transfer_to self.transfer_caller_id = transfer_caller_id diff --git a/test/unit/models/bxml/test_transfer.py b/test/unit/models/bxml/test_transfer.py index de08ff33..3f4abc35 100644 --- a/test/unit/models/bxml/test_transfer.py +++ b/test/unit/models/bxml/test_transfer.py @@ -62,8 +62,3 @@ def test_identity_to_bxml(self): ) expected = 'sip@bw.com' assert expected == transfer.to_bxml() - - def test_attestation_level_identity_omitted_by_default(self): - transfer = Transfer(transfer_to=[self.sip_uri]) - assert 'attestationLevel' not in transfer.to_bxml() - assert 'identity' not in transfer.to_bxml()