Sim, fiz um código python que le uma XML de template, troca os placeholders e gera o cancelar a nfse:
XML:
<?xml version="1.0" encoding="UTF-8"?>
<pedRegEvento xmlns="http://www.sped.fazenda.gov.br/nfse" versao="1.00">
<infPedReg Id="PRE{{order_nfse_number_val}}101101{{cancel_id_val}}">
<tpAmb>{{tpAmb_val}}</tpAmb>
<verAplic>{{verAplic_val}}</verAplic>
<dhEvento>{{actual_billing_time_val}}-03:00</dhEvento>
<CNPJAutor>{{cnpj_val}}</CNPJAutor>
<chNFSe>{{order_nfse_number_val}}</chNFSe>
<nPedRegEvento>{{cancel_id_val}}</nPedRegEvento>
<e101101>
<xDesc>Cancelamento de NFS-e</xDesc>
<cMotivo>2</cMotivo>
<xMotivo>Serviço não prestado</xMotivo>
</e101101>
</infPedReg>
</pedRegEvento>
O order_nfse_number é a chave_de_acesso da nota emitida.
No cancel_id to usando 001.
Depois disso é só assinar usando o elemento "infPedReg", comprimir com gzip, encodar pra b64 e chamar a eventos com POST. Minha chamada com requests ficou assim:
response = requests.post(f"{self.sefin_url}/nfse/{chave_acesso}/eventos", json={"pedidoRegistroEventoXmlGZipB64": compressed_xml_b64}, cert=(self.cert_path, self.key_path), headers={"Content-Type": "application/json; charset=utf-8"})
Um dos erros que estava fazendo era no POST passar o campo o campo "dpsXmlGZipB64" que uso na hora de emitir a NFSE ao invés de usar o "pedidoRegistroEventoXmlGZipB64".
É uma pena que essa API se não manda tudo exatamente como tem que ser ela não emite/cancela e também não fala o erro.
Tenho assinado em SHA1 o que acho bem ruim, mas até agora não consegui fazer nenhum outro SHA funcionar, se alguém tá conseguindo assinar com SHA256 ou algum outro e a API deles está recebendo me avisem por favor, pq tive que fazer uma enjambra pra conseguir realizar SHA1 usando a lib do signxml.