
     /i<                        U d Z ddlZddlZddlZddlZddlZddlmZmZm	Z	m
Z
mZ ddlmZ ddlmZmZ 	 ddlmZmZmZ dZ ej,                  d	      Z ej0                  ej2                  
       e G d d             Z G d d	      Zdae
e   ed<   defdZedk(  r e       Z e jC                           e"d       dddddddZ#e jI                  de#      Z% e"de%        ddddd d!d"iZ&e jI                  d#e&      Z% e"d$e%        d%d&d'd(Z'e jI                  d)e'      Z% e"d*e%         e"d+e jQ                                 yy# e$ r dZY w xY w),z
Genesis Webhook Receiver
=========================
HTTP webhook receiver for external integrations.

PM-046: Webhook Receiver
- Receives GHL, Instantly, Telnyx webhooks
- Routes to appropriate skill handlers
- Logs all webhook events
    N)DictAnyListOptionalCallable)datetime)	dataclassasdict)FlaskrequestjsonifyTFWebhookReceiver)levelc                       e Zd ZU dZeed<   eed<   eed<   eeef   ed<   eeef   ed<   eed<   eed<   e	e   ed	<   d
eeef   fdZ
y)WebhookEventz$Represents a received webhook event.event_idsource
event_typepayloadheadersreceived_at	processedprocessing_resultreturnc                     t        |       S N)r
   selfs    -/mnt/e/genesis-system/api/webhook_receiver.pyto_dictzWebhookEvent.to_dict-   s    d|    N)__name__
__module____qualname____doc__str__annotations__r   r   boolr   r     r!   r   r   r   !   sY    .MKO#s(^#s(^O}$c3h r!   r   c                   P   e Zd ZdZddddddZd%d	eeef   fd
ZdefdZdede	dede
fdZdedededdfdZ	 	 d&dedeeef   deeef   de	deeef   f
dZdedeeef   defdZdededeeef   deeef   fdZdeeef   deeef   fdZdeeef   deeef   fdZdeeef   deeef   fdZdeeef   deeef   fdZdeeef   deeef   fdZdeeef   deeef   fdZdeeef   deeef   fdZd'd Zd(ded!edeeeef      fd"Zdeeef   fd#Zdee   fd$Zy))r   z
    Webhook receiver for external integrations.
    Routes webhooks to appropriate handlers based on source and event type.
    zX-GHL-SignaturezX-Instantly-Signatureztelnyx-signature-ed25519zStripe-SignaturezX-Slack-Signature)ghl	instantlytelnyxstripeslackNsecret_keysc                    |xs i | _         | j                   j                  dt        j                  dd             | j                   j                  dt        j                  dd             | j                   j                  dt        j                  dd             i i i i d| _        g | _        t        j                  d	       y
)z
        Initialize webhook receiver.

        Args:
            secret_keys: Dict mapping source to secret key for signature verification
        r+   GHL_WEBHOOK_SECRET r,   INSTANTLY_WEBHOOK_SECRETr-   TELNYX_WEBHOOK_SECRET)r+   r,   r-   genericzWebhook Receiver initializedN)r0   
setdefaultosgetenvhandlers	event_logloggerinfo)r   r0   s     r   __init__zWebhookReceiver.__init__@   s     '," 	##E2995I2+NO##K;UWY1Z[##Hbii8OQS.TU 	9
 .023r!   r   c                 J    ddl }d|j                         j                  dd  S )zGenerate unique event ID.r   Nwh_   )uuiduuid4hex)r   rB   s     r   _generate_event_idz"WebhookReceiver._generate_event_id[   s&    TZZ\%%cr*+,,r!   r   r   	signaturec                    | j                   j                  |      }|st        j                  d| d       yt	        j
                  |j                         |t        j                        j                         }t	        j                  ||      S )a  
        Verify webhook signature.

        Args:
            source: Webhook source (ghl, instantly, telnyx)
            payload: Raw request body
            signature: Signature from header

        Returns:
            True if signature is valid
        zNo secret key configured for z, skipping verificationT)r0   getr<   warninghmacnewencodehashlibsha256	hexdigestcompare_digest)r   r   r   rF   secretexpecteds         r   _verify_signaturez!WebhookReceiver._verify_signature`   s{     !!%%f-NN:6(BYZ[ 88MMONN
 )+	 	 ""8Y77r!   r   handlerc                     || j                   vri | j                   |<   || j                   |   |<   t        j                  d| d|        y)z
        Register a handler for a specific webhook event.

        Args:
            source: Webhook source (ghl, instantly, telnyx, generic)
            event_type: Event type to handle
            handler: Handler function
        zRegistered handler for :N)r:   r<   r=   )r   r   r   rT   s       r   register_handlerz WebhookReceiver.register_handler{   sK     &$&DMM&!,3fj)-fXQzlCDr!   r   raw_bodyc           
      ^   |xs i }| j                         }|r_|| j                  v rQ| j                  |   }|j                  |d      }| j                  |||      st        j                  d|        d|dS | j                  ||      }t        |||||t        j                         j                         dd      }	| j                  j                  |	       t        j                  d| d	| d
| d       | j                  |||      }
d|	_        |
j                  dd      |	_        |d||
dS )a*  
        Receive and process a webhook event.

        Args:
            source: Webhook source
            payload: Parsed payload (dict)
            headers: Request headers
            raw_body: Raw request body for signature verification

        Returns:
            Processing result
        r3   z#Invalid signature for webhook from invalid_signature)errorr   FN)r   r   r   r   r   r   r   r   zReceived webhook: rV   z (ID: )Tstatusr   received)r   r]   r   result)rE   SIGNATURE_HEADERSrH   rS   r<   rI   _extract_event_typer   r   utcnow	isoformatr;   appendr=   _route_to_handlerr   r   )r   r   r   r   rX   r   
sig_headerrF   r   eventr_   s              r   receivezWebhookReceiver.receive   sF    -R**, $"8"88//7JJ3I))&(IF!DVHMN!4(KK --fg>
 ! )335"	
 	e$(*VH:QOP ''
GD "(**X{"C ! $	
 	
r!   c           
      P   |dk(  r"|j                  d|j                  dd            S |dk(  r"|j                  d|j                  dd            S |dk(  r"|j                  di       j                  dd      S |j                  d|j                  d|j                  dd                  S )	z0Extract event type from payload based on source.r+   rg   typeunknownr,   r   r-   data)rH   )r   r   r   s      r   ra   z#WebhookReceiver._extract_event_type   s    U?;;wFI(FGG{";;|W[[)-LMMx;;vr*..|YGG;;|W[['++fV_B`-abbr!   c           	      $   || j                   v r*|| j                   |   v r	  | j                   |   |   |      S || j                   j                  di       v r	  | j                   d   |   |      S t        j                  d	| d| d
       dddS # t        $ r6}t        j                  d| d| d|        dt	        |      dcY d}~S d}~ww xY w# t        $ r3}t        j                  d| d|        dt	        |      dcY d}~S d}~ww xY w)z#Route event to appropriate handler.zHandler error for rV   z: r[   )r]   messageNr6   zGeneric handler error for zNo handler for z, acknowledgingacknowledgedzNo handler registered)r:   	Exceptionr<   r[   r&   rH   r=   )r   r   r   r   es        r   re   z!WebhookReceiver._route_to_handler   s    T]]"zT]]65J'J>8t}}V,Z8AA **9b99>;t}}Y/
;GDD 	ofXQzl/JK(5LMM  >1&:,bLM")c!f==>  >9*RsKL")c!f==>s;   B C 	C+CCC	D(D
D
Dc                     |j                  d|      }t        j                  d|j                  dd              dd|j                  d      dS )	z!Handle GHL contact.created event.contactzGHL Contact Created: emailrk   r   contact_createdid)r]   action
contact_idrH   r<   r=   )r   r   rs   s      r   handle_ghl_contact_createdz*WebhookReceiver.handle_ghl_contact_created   sP    ++i1+GKK,K+LMN "'!++d+
 	
r!   c                     |j                  d|      }t        j                  d|j                  d              dd|j                  d      |j                  d      dS )z%Handle GHL opportunity status change.opportunityzGHL Opportunity Updated: rv   r   opportunity_updatedstage)r]   rw   opportunity_idr~   ry   )r   r   r|   s      r   handle_ghl_opportunity_updatedz.WebhookReceiver.handle_ghl_opportunity_updated   sZ    kk-9/0E/FGH "+)ood3 __W-	
 	
r!   c                     |j                  dd      }|j                  dd      }t        j                  d| d| d       dd||d	S )
z$Handle Instantly email opened event.rt   rk   campaign_idzInstantly Email Opened: z (Campaign: r\   r   email_opened)r]   rw   rt   r   ry   )r   r   rt   r   s       r   handle_instantly_email_openedz-WebhookReceiver.handle_instantly_email_opened  sV    GY/kk-;.ug\+aPQ "$&	
 	
r!   c                     |j                  dd      }t        j                  d|        dd||j                  dd      dd	 d
S )z&Handle Instantly reply received event.rt   rk   zInstantly Reply Received: r   reply_received
reply_textr3   Nd   )r]   rw   rt   r   ry   )r   r   rt   s      r   handle_instantly_reply_receivedz/WebhookReceiver.handle_instantly_reply_received  sP    GY/089 "&!++lB7=	
 	
r!   c                     |j                  di       j                  di       }|j                  dd      }t        j                  d|        dd||j                  d      |j                  d	      d
S )z#Handle Telnyx call.initiated event.rl   r   call_control_idrk   zTelnyx Call Initiated: r   call_initiatedfromto)r]   rw   r   r   r   ry   r   r   rl   call_ids       r   handle_telnyx_call_initiatedz,WebhookReceiver.handle_telnyx_call_initiated  sp    {{62&**9b9((,i8-gY78 "&&HHV$((4.
 	
r!   c                     |j                  di       j                  di       }|j                  dd      }t        j                  d|        dd|dS )	z"Handle Telnyx call.answered event.rl   r   r   rk   zTelnyx Call Answered: r   call_answered)r]   rw   r   ry   r   s       r   handle_telnyx_call_answeredz+WebhookReceiver.handle_telnyx_call_answered-  sY    {{62&**9b9((,i8,WI67 "%&
 	
r!   c                     |j                  di       j                  di       }|j                  dd      }t        j                  d|        dd||j                  di       j                  d	d
      dS )z Handle Telnyx call.hangup event.rl   r   r   rk   zTelnyx Call Hangup: r   call_hangupcall_leg_end_reasonduration_secondsr   )r]   rw   r   r   ry   r   s       r   handle_telnyx_call_hangupz)WebhookReceiver.handle_telnyx_call_hangup9  sw    {{62&**9b9((,i8*7)45 "#& $)> C G GHZ\] ^	
 	
r!   c                 8   | j                  dd| j                         | j                  dd| j                         | j                  dd| j                         | j                  dd| j                         | j                  dd| j                         | j                  dd| j                         | j                  d	d
| j
                         | j                  d	d| j                         | j                  d	d| j                         t        j                  d       y)z,Register default handlers for common events.r+   contact.createdContactCreatezopportunity.updatedOpportunityUpdater,   r   r   r-   call.initiatedzcall.answeredzcall.hangupz#Default webhook handlers registeredN)
rW   rz   r   r   r   r   r   r   r<   r=   r   s    r   setup_default_handlersz&WebhookReceiver.setup_default_handlersF  s     	e%68W8WXe_d6U6UVe%:D<_<_`e%8$:]:]^ 	k>4;];]^k+;T=a=ab 	h(8$:[:[\h9Y9YZht7U7UV9:r!   limitc                     | j                   | d }|r|D cg c]  }|j                  |k(  s| }}|D cg c]  }|j                          c}S c c}w c c}w )z-Get event log, optionally filtered by source.N)r;   r   r    )r   r   r   eventsrq   s        r   get_event_logzWebhookReceiver.get_event_logY  sT    (!'>A188v+=a>F>%+,		,, ?,s   AAAc           	         | j                   sddiS i }i }d}| j                   D ]l  }|j                  |j                  d      dz   ||j                  <   |j                  |j                  d      dz   ||j                  <   |j                  sh|dz  }n t        | j                         |||t        d | j                  j                         D              dS )zGet webhook receiver metrics.total_eventsr      c              3   2   K   | ]  }t        |        y wr   )len).0hs     r   	<genexpr>z.WebhookReceiver.get_metrics.<locals>.<genexpr>t  s     &N!s1v&Ns   )r   r   	by_sourceby_typehandlers_registered)	r;   rH   r   r   r   r   sumr:   values)r   r   r   processed_countrg   s        r   get_metricszWebhookReceiver.get_metrics`  s    ~~"A&&	^^ 	%E&/mmELL!&Dq&HIell#(/E4D4Da(H1(LGE$$%1$		%  /("#&&Nt}}7K7K7M&N#N
 	
r!   c                     t         st        j                  d       yt        t              }| |j                  ddg      fd       }|j                  ddg      fd	       }|S )
z(Create Flask app with webhook endpoints.zFlask not available.Nz/webhooks/<source>POST)methodsc                     t        j                  d      }t        t         j                        }t        j                         }j                  | |||      }t        |      S )NT)force)r   get_jsondictr   get_datarh   r   )r   r   r   rX   r_   receivers        r   webhook_handlerz9WebhookReceiver.create_flask_app.<locals>.webhook_handler  sO    &&T2G7??+G'')H%%fgwIF6?"r!   z/webhooks/healthGETc                  <    t        d j                         d      S )Nhealthy)r]   metrics)r   r   )r   s   r   webhook_healthz8WebhookReceiver.create_flask_app.<locals>.webhook_health  s$    ##//1  r!   )FLASK_AVAILABLEr<   rI   r   r"   route)r   appr   r   r   s       @r   create_flask_appz WebhookReceiver.create_flask_appy  sq    NN12Ho	'&	:	# 
;	# 
%w	7	 
8	 
r!   r   )NN)r   N)Nr   ) r"   r#   r$   r%   r`   r   r&   r>   rE   bytesr(   rS   r   rW   r   rh   ra   re   rz   r   r   r   r   r   r   r   intr   r   r   r   r   r)   r!   r   r   r   1   s    !,,$$4DcN 46-C -
8 8e 8%(8-186Es E E"*E/3E" +/"&:
c :
DcN :
c3h:
:
+/S>:
x	c# 	cS#X 	c3 	cN N N#'S>N6:38nN2	
$sCx. 	
T#s(^ 	


d38n 

cSVh 


T#s(^ 
SRUX 


tCH~ 

$sTWx. 


DcN 
tCQTH~ 


4S> 

d3PS8n 


c3h 
DcN 
;&-C -s -T$sTWx.EY -
T#s(^ 
2(3- r!   	_receiverr   c                  V    t         t               a t         j                          t         S )z&Get or create global webhook receiver.)r   r   r   r)   r!   r   get_receiverr     s$     #%	((*r!   __main__z
=== Webhook Receiver Test ===r   con_123ztest@example.comTestContact)rv   rt   
first_name	last_name)rg   rs   r+   zGHL Webhook: rl   r   call_xyz123z+15551234567z+15559876543)r   r   r   )r   r   r-   zTelnyx Webhook: r   zlead@example.comcamp_abc)r   rt   r   r,   zInstantly Webhook: z

Metrics: ))r%   r8   jsonrJ   rM   loggingtypingr   r   r   r   r   r   dataclassesr	   r
   flaskr   r   r   r   ImportError	getLoggerr<   basicConfigINFOr   r   r   r'   r   r"   r   r   printghl_payloadrh   r_   telnyx_payloadinstantly_payloadr   r)   r!   r   <module>r      s  	 
     6 6  )--O 
		,	-   ',, '   a aJ (,	8O$ +o  z H##%	
+, #' "	
K e[1F	M&
"# 	*#0&$
	N h7F	VH
%& %#!
 k+<=F	x
() 
K,,./
01Y Q  Os   E
 
EE