Skip to content

Instantly share code, notes, and snippets.

@mercul3s
Last active January 2, 2025 18:56
Show Gist options
  • Save mercul3s/9dbf18bc5f1b63bc56e24b2c6c1cf872 to your computer and use it in GitHub Desktop.
Save mercul3s/9dbf18bc5f1b63bc56e24b2c6c1cf872 to your computer and use it in GitHub Desktop.
TAP Callback

We use an AWS Lambda function for the callback handler. It accepts requests with query params for the qualification (complete or quality_term) and the tap id, which are passed through Survey Monkey on survey completion. The hash is generated and appended to the callback URL as outlined in the documentation here: http://docs.tapresearch.com/#callbacks The function then calls that URL with a GET and logs the callback URL and any success/failure. Though the callback URL returns a 200 request, we never see successfully completions in TAP campaigns. We end up seeing incompletes, abandons, and Failed partner callback security check.

import hashlib
import hmac
import logging
import os
import sys
sys.path.insert(0, "src/vendor")
import requests
app_version = "1.0.0"
logger = logging.getLogger(name="tap-callback-handler")
logger.setLevel(os.environ.get("LOG_LEVEL", "INFO"))
tap_base_url = f"{os.environ.get('TAP_BASE_URL')}/cps/"
# TAP Base URL:
# https://www.tapresearch.com/router/customers/0fa67175621bb1db8cfca37fc351c642
# Example Callback URL:
# https://www.tapresearch.com/router/customers/0fa67175621bb1db8cfca37fc351c642/cps/complete?tid=39fd5a382f6116147d9a7a7cb1b96a6d&tr_sech=5bca903a568f9e003fc1f6988bffe2b2612d4919
def callback(event, context):
"""
AWS Lambda handler function to process survey callback requests from Survey Monkey.
This function extracts qualification and tid parameters from the event,
generates a callback URL based on the qualification (complete or , sends a GET request to that URL, and logs the result.
Args:
event (dict): The AWS Lambda event object containing request data.
context (object): The AWS Lambda context object.
Returns:
dict: A response object with a 200 status code and a thank you message.
Raises:
None, but logs errors if the request to TAP fails.
"""
params = event.get("queryStringParameters")
qualification = params.get("qualification")
tid = params.get("tid")
callback_url = generate_url(qualification, tid)
logger.debug(callback_url)
result = requests.get(callback_url)
if result.ok:
logger.debug(f"Successfully sent request to TAP: {tid} {qualification}")
else:
logger.error(
f"Error sending request to TAP: {result.reason} TAP_ID: {tid} QUALIFICATION: {qualification}"
)
return {"statusCode": 200, "body": "Thank you for participating in the survey!"}
def generate_url(qualification, tid):
"""
Generate a secure URL for TAP callback.
This function creates a URL with the given qualification and tid,
then adds a security hash using HMAC-SHA1 with a secret key.
Args:
qualification (str): The qualification parameter for the URL.
tid (str): The tid parameter for the URL.
Returns:
str: The generated URL with the security hash appended.
Raises:
None, but may raise exceptions if encoding or hashing fails.
"""
tap_secret = get_tap_secret()
callback_url = f"{tap_base_url}{qualification}?tid={tid}"
security_hash = hmac.new(
tap_secret.encode("utf-8"),
callback_url.encode("utf-8"),
hashlib.sha1,
)
hashed_url = f"{callback_url}&tr_sech={security_hash.hexdigest()}"
return hashed_url
def get_tap_secret():
"""
Retrieve the TAP secret from environment variables or AWS Secrets Manager.
This function first checks for the TAP_SECRET in environment variables.
If not found, it attempts to retrieve the secret from AWS Secrets Manager
using the AWS Lambda Secrets Manager extension.
Returns:
str: The TAP secret string.
Raises:
None, but logs an error if the secret cannot be retrieved from Secrets Manager.
"""
tap_secret = os.environ.get("TAP_SECRET")
if tap_secret:
return tap_secret
else:
secrets_extension_endpoint = (
"http://localhost:2773/secretsmanager/get?secretId=tap-secret"
)
headers = {
"X-Aws-Parameters-Secrets-Token": os.environ.get("AWS_SESSION_TOKEN")
}
response = requests.get(secrets_extension_endpoint, headers=headers)
tap_secret = response.json().get("SecretString")
return tap_secret
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment