fromdataclassesimportdataclassfromhashlibimportsha256fromtimeimporttimefromtypingimportListfromfastapiimportFastAPI,Body,Headerfromstarlette.requestsimportRequestfromstarlette.responsesimportResponse@dataclassclassSignatures:timestamp:intv1:List[str]@staticmethoddeffrom_string(string:str)->"Signatures":s=Signatures(0,[])forlineinstring.split(","):key,value=line.split("=")ifkey=="t":s.timestamp=int(value)elifkey=="v1":s.v1.append(value)returnsdefsign_payload(secret:str,payload:str)->str:m=sha256()m.update(secret.encode("utf-8"))m.update(b".")m.update(payload.encode("utf-8"))returnm.digest().hex()defverify(secret:str,payload:str,signatures:Signatures)->bool:signature=sign_payload(secret,payload)current_time=time()ifcurrent_time>signatures.timestamp+300:# Allow a 5 minutes tolerancereturnFalsereturnsignatureinsignatures.v1app=FastAPI(title="Example Webhook Callback")_SECRET_KEY="..."@app.post("/prosa-webhook")asyncdefprosa_webhook(request:Request,body:dict=Body(...),prosa_signature:str=Header(...,alias="X-Prosa-Signature"),prosa_event_id:str=Header(...,alias="X-Prosa-Event-UUID"),prosa_event_type:str=Header(...,alias="X-Prosa-Event"),):signatures=Signatures.from_string(prosa_signature)payload=(awaitrequest.body()).decode("utf-8")verified=verify(_SECRET_KEY,payload,signatures)ifnotverified:# Reject unverified eventsreturnResponse(status_code=400)event={"event_id":prosa_event_id,"event_type":prosa_event_type,"body":body}# Do something with the webhook eventreturnResponse()
Parsing Prosa Webhook Events
All webhook events are invoked using HTTPS with the following scheme: