def address_webhook(request, secret_key, ignored_key): ''' Process an inbound webhook from blockcypher ''' # Log webhook webhook = WebHook.log_webhook(request, WebHook.BLOCKCYPHER_ADDRESS_NOTIFICATION) assert secret_key == WEBHOOK_SECRET_KEY assert request.method == 'POST', 'Request has no post' blockcypher_id = request.META.get('HTTP_X_EVENTID') assert 'tx-confirmation' == request.META.get('HTTP_X_EVENTTYPE') payload = json.loads(request.body.decode()) address_subscription = AddressSubscription.objects.get( blockcypher_id=blockcypher_id) tx_hash = payload['hash'] num_confs = payload['confirmations'] double_spend = payload['double_spend'] satoshis_sent = payload['total'] fee_in_satoshis = payload['fees'] tx_event = get_object_or_None( OnChainTransaction, tx_hash=tx_hash, address_subscription=address_subscription, ) if tx_event: tx_is_new = False tx_event.num_confs = num_confs tx_event.double_spend = double_spend tx_event.save() else: tx_is_new = True input_addresses = set() for input_entry in payload['inputs']: for address in input_entry.get('addresses', []): input_addresses.add(address) if address_subscription.b58_address in input_addresses: is_withdrawal = True else: is_withdrawal = False output_addresses = set() for output_entry in payload.get('outputs', []): for address in output_entry['addresses']: output_addresses.add(address) if address_subscription.b58_address in output_addresses: is_deposit = True else: is_deposit = False tx_event = OnChainTransaction.objects.create( tx_hash=tx_hash, address_subscription=address_subscription, num_confs=num_confs, double_spend=double_spend, satoshis_sent=satoshis_sent, fee_in_satoshis=fee_in_satoshis, is_deposit=is_deposit, is_withdrawal=is_withdrawal, ) # email sending logic # TODO: add logic for notify on deposit vs withdrawal # TODO: add safety check to prevent duplicate email sending if tx_event.address_subscription.unsubscribed_at or tx_event.address_subscription.disabled_at: # unsubscribe from webhooks going forward try: unsub_result = unsubscribe_from_webhook( webhook_id=tx_event.address_subscription.blockcypher_id, api_key=BLOCKCYPHER_API_KEY, coin_symbol=tx_event.address_subscription.coin_symbol, ) assert unsub_result is True, unsub_result except Exception: # there was a problem unsubscribing # notify using sentry but still return the webhook to blockcypher client.captureException() elif tx_event.address_subscription.auth_user.email_verified: # make sure we haven't contacted too many times (and unsub if so) earliest_dt = now() - timedelta(days=3) recent_emails_sent = SentEmail.objects.filter( address_subscription=tx_event.address_subscription, sent_at__gt=earliest_dt, ).count() if recent_emails_sent > 100: # too many emails, unsubscribe tx_event.address_subscription.admin_unsubscribe_subscription() client.captureMessage('TX Event %s unsubscribed' % tx_event.id) # TODO: notify user they've been unsubscribed else: # proceed with normal email sending if double_spend and (tx_is_new or not tx_event.double_spend): # We have the first reporting of a double-spend tx_event.send_double_spend_tx_notification() elif num_confs == 0 and tx_is_new: # First broadcast if tx_event.address_subscription.notify_on_broadcast: if tx_event.is_deposit and tx_event.address_subscription.notify_on_deposit: tx_event.send_unconfirmed_tx_email() elif tx_event.is_withdrawal and tx_event.address_subscription.notify_on_withdrawal: tx_event.send_unconfirmed_tx_email() elif num_confs == 6: # Sixth confirm if tx_event.address_subscription.notify_on_sixth_confirm: if tx_event.is_deposit and tx_event.address_subscription.notify_on_deposit: tx_event.send_confirmed_tx_email() elif tx_event.is_withdrawal and tx_event.address_subscription.notify_on_withdrawal: tx_event.send_confirmed_tx_email() else: # active subscription with unverfied email (can't contact) # TODO: add unsub if orig subscription is > X days old # eventually these could pile up pass # Update logging webhook.address_subscription = address_subscription webhook.succeeded = True webhook.save() # Return something return HttpResponse("*ok*")
def address_webhook(request, secret_key, ignored_key): ''' Process an inbound webhook from blockcypher ''' # Log webhook webhook = WebHook.log_webhook(request, WebHook.BLOCKCYPHER_ADDRESS_NOTIFICATION) assert secret_key == WEBHOOK_SECRET_KEY assert request.method == 'POST', 'Request has no post' blockcypher_id = request.META.get('HTTP_X_EVENTID') assert 'tx-confirmation' == request.META.get('HTTP_X_EVENTTYPE') payload = json.loads(request.body.decode()) address_subscription = AddressSubscription.objects.get(blockcypher_id=blockcypher_id) tx_hash = payload['hash'] num_confs = payload['confirmations'] double_spend = payload['double_spend'] satoshis_sent = payload['total'] fee_in_satoshis = payload['fees'] tx_event = get_object_or_None( OnChainTransaction, tx_hash=tx_hash, address_subscription=address_subscription, ) if tx_event: tx_is_new = False tx_event.num_confs = num_confs tx_event.double_spend = double_spend tx_event.save() else: tx_is_new = True input_addresses = set() for input_entry in payload['inputs']: for address in input_entry.get('addresses', []): input_addresses.add(address) if address_subscription.b58_address in input_addresses: is_withdrawal = True else: is_withdrawal = False output_addresses = set() for output_entry in payload.get('outputs', []): for address in output_entry['addresses']: output_addresses.add(address) if address_subscription.b58_address in output_addresses: is_deposit = True else: is_deposit = False tx_event = OnChainTransaction.objects.create( tx_hash=tx_hash, address_subscription=address_subscription, num_confs=num_confs, double_spend=double_spend, satoshis_sent=satoshis_sent, fee_in_satoshis=fee_in_satoshis, is_deposit=is_deposit, is_withdrawal=is_withdrawal, ) # email sending logic # TODO: add logic for notify on deposit vs withdrawal # TODO: add safety check to prevent duplicate email sending if tx_event.address_subscription.unsubscribed_at or tx_event.address_subscription.disabled_at: # unsubscribe from webhooks going forward try: unsub_result = unsubscribe_from_webhook( webhook_id=tx_event.address_subscription.blockcypher_id, api_key=BLOCKCYPHER_API_KEY, coin_symbol=tx_event.address_subscription.coin_symbol, ) assert unsub_result is True, unsub_result except Exception: # there was a problem unsubscribing # notify using sentry but still return the webhook to blockcypher client.captureException() elif tx_event.address_subscription.auth_user.email_verified: # make sure we haven't contacted too many times (and unsub if so) earliest_dt = now() - timedelta(days=3) recent_emails_sent = SentEmail.objects.filter( address_subscription=tx_event.address_subscription, sent_at__gt=earliest_dt, ).count() if recent_emails_sent > 100: # too many emails, unsubscribe tx_event.address_subscription.admin_unsubscribe_subscription() client.captureMessage( 'TX Event %s unsubscribed' % tx_event.id, data={ 'tx_event': tx_event.id, 'address_subscription': tx_event.address_subscription, 'recent_emails_sent': recent_emails_sent, 'webhook': webhook.id, }, ) # TODO: notify user they've been unsubscribed else: # proceed with normal email sending if double_spend and (tx_is_new or not tx_event.double_spend): # We have the first reporting of a double-spend tx_event.send_double_spend_tx_notification() elif num_confs == 0 and tx_is_new: # First broadcast if tx_event.address_subscription.notify_on_broadcast: if tx_event.is_deposit and tx_event.address_subscription.notify_on_deposit: tx_event.send_unconfirmed_tx_email() elif tx_event.is_withdrawal and tx_event.address_subscription.notify_on_withdrawal: tx_event.send_unconfirmed_tx_email() elif num_confs == 6: # Sixth confirm if tx_event.address_subscription.notify_on_sixth_confirm: if tx_event.is_deposit and tx_event.address_subscription.notify_on_deposit: tx_event.send_confirmed_tx_email() elif tx_event.is_withdrawal and tx_event.address_subscription.notify_on_withdrawal: tx_event.send_confirmed_tx_email() else: # active subscription with unverfied email (can't contact) # TODO: add unsub if orig subscription is > X days old # eventually these could pile up pass # Update logging webhook.address_subscription = address_subscription webhook.succeeded = True webhook.save() # Return something return HttpResponse("*ok*")