From 2acb57d9d0e54e44b0e2a8102a99a766c5d44f4d Mon Sep 17 00:00:00 2001 From: Nicolas Asanov Date: Tue, 20 Oct 2026 17:10:15 -0400 Subject: [PATCH 1/4] 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/4] 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() From e16a51f798b1b6a8bf4cf58da8e52f11a78263df Mon Sep 17 00:00:00 2001 From: Nicolas Asanov Date: Wed, 11 Nov 2026 11:33:46 -0500 Subject: [PATCH 3/4] refactor(VAPI-4422): rename Transfer attestationLevel to requestedAttestationLevel --- bandwidth/models/bxml/verbs/transfer.py | 10 +++++----- test/unit/models/bxml/test_transfer.py | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bandwidth/models/bxml/verbs/transfer.py b/bandwidth/models/bxml/verbs/transfer.py index 37832dbe..37535b3a 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, attestation_level: str=None, identity: str=None + privacy: bool=None, requested_attestation_level: str=None, identity: str=None ): """Initialize a verb @@ -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 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. + requested_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 requested_attestation_level. Defaults to None. """ self.transfer_to = transfer_to self.transfer_caller_id = transfer_caller_id @@ -82,7 +82,7 @@ def __init__( self.diversion_treatment = diversion_treatment self.diversion_reason = diversion_reason self.privacy = privacy - self.attestation_level = attestation_level + self.requested_attestation_level = requested_attestation_level self.identity = identity super().__init__( tag="Transfer", @@ -107,7 +107,7 @@ def _attributes(self): "tag": self.tag, "diversionTreatment": self.diversion_treatment, "diversionReason": self.diversion_reason, - "attestationLevel": self.attestation_level, + "requestedAttestationLevel": self.requested_attestation_level, "identity": self.identity } diff --git a/test/unit/models/bxml/test_transfer.py b/test/unit/models/bxml/test_transfer.py index 3f4abc35..fa004e2d 100644 --- a/test/unit/models/bxml/test_transfer.py +++ b/test/unit/models/bxml/test_transfer.py @@ -38,7 +38,7 @@ def setUp(self): fallback_password="fallbackPass", diversion_treatment="propagate", diversion_reason="away", - attestation_level="A" + requested_attestation_level="A" ) def test_instance(self): @@ -47,11 +47,11 @@ 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() From c77e34421f66861f67e1b93d62f2faf075a13882 Mon Sep 17 00:00:00 2001 From: Nicolas Asanov Date: Wed, 11 Nov 2026 11:44:16 -0500 Subject: [PATCH 4/4] test(VAPI-4422): fold Transfer attestation and identity into existing tests --- test/unit/models/bxml/test_transfer.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/test/unit/models/bxml/test_transfer.py b/test/unit/models/bxml/test_transfer.py index fa004e2d..9db92b04 100644 --- a/test/unit/models/bxml/test_transfer.py +++ b/test/unit/models/bxml/test_transfer.py @@ -38,7 +38,8 @@ def setUp(self): fallback_password="fallbackPass", diversion_treatment="propagate", diversion_reason="away", - requested_attestation_level="A" + requested_attestation_level="A", + identity="eyJhbGciOiJFUzI1NiJ9.e30.c2ln;info=;ppt=\"shaken\"" ) def test_instance(self): @@ -47,18 +48,10 @@ 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()