Webhook verification in FastAPI

Options

i have a function to create and update webhook

logging.basicConfig(filename='automation.log', level=logging.INFO)

smartsheet_client = smartsheet.Smartsheet(SMARTSHEET_API_KEY)


def create_webhook(sheet_id: int, callback_url: str):

    """Create a webhook for the specified sheet and verify it."""

    # Create the webhook

    webhook = smartsheet_client.Webhooks.create_webhook(

        smartsheet.models.Webhook({

            'name': 'Puneet',

            'scope': 'sheet',

            'scopeObjectId': sheet_id,

            'callbackUrl': callback_url,

            'events': ['*.*'],

            'version': 1

        })

    )


    logging.info(f"Webhook created successfully: {webhook}")


    # Enable the webhook to initiate the verification process

    update_webhook(webhook.result.id)


    # Wait for verification to complete

    while True:

        time.sleep(5)  # Wait for 5 seconds before checking again

        updated_webhook = get_webhook(webhook.result.id)

        if updated_webhook.status == 'ENABLED':

            logging.info("Webhook verification completed successfully.")

            break

        elif updated_webhook.status == 'DISABLED_VERIFICATION_FAILED':

            logging.error("Webhook verification failed.")

            break


def update_webhook(webhook_id: int):

    """Update the enabled status of the webhook."""

    Webhook = smartsheet_client.Webhooks.update_webhook(

    webhook_id,       # webhook_id

    smartsheet_client.models.Webhook({

        'enabled': True}))


    print(Webhook)


def get_webhook(webhook_id: int):

    """Retrieve the details of the specified webhook."""

    return smartsheet_client.Webhooks.get_webhook(webhook_id)




I also have a endpoint for my verification request


@app.post("/webhook")

async def handle_webhook(challenge: str, webhookId: int, smartsheet_hook_challenge: str = Header(None)):

    # Verify that the received challenge matches the value in the header

    if challenge != smartsheet_hook_challenge:

        raise HTTPException(status_code=400, detail="Invalid challenge")


    # Construct the response containing the same challenge value

    response_body = {"smartsheetHookResponse": challenge}


    # Return the constructed response with a 200 status code

    return response_body


But my endpoint is not receiving any request for verification, please help

Best Answer

  • Puneet_Bajaj_IITM
    Answer ✓
    Options

    Ya, i tried using ngrook and it worked, was in problem since 3 days. I think smarsheet doesnt support github codespace urls .

    Thanks for your Time and Effort, I appreciate this community and the effort you have put in for me.

Answers

  • Lee Joramo
    Lee Joramo ✭✭✭✭✭✭
    edited 02/28/24
    Options

    I have only written Smartsheet webhook's using the Javascript SKD and NodeJS, although I am also a Python programmer. I typically use Javascirpt for our web applications that integrate with Smartsheet, and Python for data pipeline tools. I don't see anything obviously wrong in a quick glance at your code.

    If you have the ability to run NodeJS, I recommend that you try Smartsheet's Webhook Sample:

    https://github.com/smartsheet-samples/node-webhook-sample

    alternatively, perhaps you can find some Python Code that is KNOWN TO WORK.

    For me, running Smartsheet's Node Webhook Sample worked when running behind Ngrok (see the sample code), but not directly via my Nginx web server. Smartsheet's error messages were very minimal. This led me to check my Nginx and SSL configuration.

    Smartsheet webhook's are very sensitive to your SSL setup. I had issues with both using a wildcard (*.example.com) certificate and the SSL configuration of your Nginx web server. To solve these issues I ran a SSL Labs Report. We started with a "Grade of B" due to supporting some older protocols, and other issues. Resolving this these issues and obtaining a "Grade of A" fixed my issues with the receiving webhook requests from Smartsheet..

    For more help related to your above code, please provide the responses returned by:

    • smartsheet_client.Webhooks.update_webhook(...)
    • smartsheet_client.models.Webhook(...)
    • smartsheet_client.Webhooks.get_webhook(webhook_id)


    Hint: in this Smartsheet Community discussion forum, the paragraph symbol ¶ to the left of the line you are currently typing allows you to format you text in various ways, including as code this will make any pasted programming code or console messages much easier to read

    good luck

  • Hi Lee,

    I am using github codespaces for development and it uses https, i think there should not be any ssl issue,

    And the data you asked is as follows

    smartsheet_client.Webhooks.update_webhook
     
    {"data": {"callbackUrl": "https://literate-xylophone-v6vr9pxjx5jrh6ppw-8000.app.github.dev/webhook", "createdAt": "2024-02-29T12:11:27+00:00Z", "disabledDetails": "Request returned HTTP status code 404 (ref id: youhoi)", "enabled": false, "events": ["*.*"], "id": 5489852901812100, "modifiedAt": "2024-02-29T12:11:29+00:00Z", "name": "Puneet", "scope": "sheet", "scopeObjectId": 4724467242585988, "sharedSecret": "23otqlndpxk9zo3ozq26n0jx2b", "status": "DISABLED_VERIFICATION_FAILED", "subscope": {}, "version": 1}, "message": "SUCCESS", "result": {"callbackUrl": "https://literate-xylophone-v6vr9pxjx5jrh6ppw-8000.app.github.dev/webhook", "createdAt": "2024-02-29T12:11:27+00:00Z", "disabledDetails": "Request returned HTTP status code 404 (ref id: youhoi)", "enabled": false, "events": ["*.*"], "id": 5489852901812100, "modifiedAt": "2024-02-29T12:11:29+00:00Z", "name": "Puneet", "scope": "sheet", "scopeObjectId": 4724467242585988, "sharedSecret": "23otqlndpxk9zo3ozq26n0jx2b", "status": "DISABLED_VERIFICATION_FAILED", "subscope": {}, "version": 1}, "resultCode": 0}
    
    
    smartsheet_client.Webhooks.get_webhook(webhook_id) 
    
    {"callbackUrl": "https://literate-xylophone-v6vr9pxjx5jrh6ppw-8000.app.github.dev/webhook", "createdAt": "2024-02-29T12:11:27+00:00Z", "disabledDetails": "Request returned HTTP status code 404 (ref id: youhoi)", "enabled": false, "events": ["*.*"], "id": 5489852901812100, "modifiedAt": "2024-02-29T12:11:29+00:00Z", "name": "Puneet", "scope": "sheet", "scopeObjectId": 4724467242585988, "sharedSecret": "23otqlndpxk9zo3ozq26n0jx2b", "status": "DISABLED_VERIFICATION_FAILED", "subscope": {}, "version": 1}
    

    I have to develop this project in FastAPI, i am not receiving any verification request. It would be very helpful if you can resolve the issue.

    Thank you

  • Lee Joramo
    Lee Joramo ✭✭✭✭✭✭
    edited 02/29/24
    Options

    Well, you are getting the standard `HTTP 404 Page Not Found`. This most likely means that GitHub Spaces is not connecting to your program and is directly returning a 404.

    UNLESS, your python program is itself returning a 404 error, in which case you would need to find out why. For this to be the case, I assume that you would need to have written code to return a 404. (It is possible that, FastAPI can return a 404 by default, but I doubt that is the case.)

    As I mentioned above I haven't done Smartsheet webhook programming in Python. And while I have done lots of Python web programming over many years, I have not used FastAPI nor GitHub CodeSpaces. So I am limited in what I can offer.

    My one recommendation is to make sure that your code is running and available to the world. Try adding this route to your code:

    @app.get("/serverStatus")
    async def check_server_status():
        return {"status": "Server is running."}
    

    You can now go to https://literate-xylophone-v6vr9pxjx5jrh6ppw-8000.app.github.dev/serverStatus in your browser and confirm it is running.

    Again, I don't know GitHub CodeSpace's but I find the 8000 in the URL interesting. I am wondering if that is a clue it is actually running on port 8000 so that the URL should be https://literate-xylophone-v6vr9pxjx5jrh6ppw-8000.app.github.dev:8000/serverStatus

  • The main issue is that i am not recieving request from smartsheet, my url is accessible and it works, i have checked. you can see my full script


    import logging
    import requests
    import smartsheet
    import time
    from typing import List
    from config import SMARTSHEET_API_KEY, PRIMARY_SHEET_ID, SECONDARY_SHEET_ID
    
    
    # Configure logging
    logging.basicConfig(filename='automation.log', level=logging.INFO)
    
    
    # Initialize Smartsheet client
    smartsheet_client = smartsheet.Smartsheet(SMARTSHEET_API_KEY)
    
    
    def create_webhook(sheet_id: int, callback_url: str):
        """Create a webhook for the specified sheet and verify it."""
        try:
            # Create the webhook
            webhook = smartsheet_client.Webhooks.create_webhook(
                smartsheet.models.Webhook({
                    'name': 'Puneet',
                    'scope': 'sheet',
                    'scopeObjectId': sheet_id,
                    'callbackUrl': callback_url,
                    'events': ['*.*'],
                    'version': 1
                })
            )
    
    
            logging.info(f"Webhook created successfully: {webhook}")
    
    
            # Enable the webhook to initiate the verification process
            update_webhook(webhook.result.id)
    
    
            # Wait for verification to complete
            while True:
                time.sleep(5)  # Wait for 5 seconds before checking again
                updated_webhook = get_webhook(webhook.result.id)
                if updated_webhook.status == 'ENABLED':
                    logging.info("Webhook verification completed successfully.")
                    break
                elif updated_webhook.status == 'DISABLED_VERIFICATION_FAILED':
                    logging.error("Webhook verification failed.")
                    update_webhook(webhook.result.id)
                    updated_webhook = get_webhook(webhook.result.id)
                    if updated_webhook.status == 'ENABLED':
                        logging.info("Webhook verification completed successfully.")
                        break
                    elif updated_webhook.status == 'DISABLED_VERIFICATION_FAILED':
                        logging.error("Webhook verification failed.")
                    break
        except Exception as e:
            logging.error(f"Error creating webhook: {e}")
    
    
    def update_webhook(webhook_id: int):
        """Update the enabled status of the webhook."""
        try:
            smartsheet_client_models_Webhook = smartsheet_client.models.Webhook({'enabled': True})
            smartsheet_client_Webhooks_update_webhook = smartsheet_client.Webhooks.update_webhook(
                webhook_id, 
                smartsheet_client_models_Webhook
            )
    
    
            logging.info("Webhook updated successfully")
        except Exception as e:
            logging.error(f"Error updating webhook: {e}")
    
    
    def get_webhook(webhook_id: int):
        """Retrieve the details of the specified webhook."""
        try:
            webhook = smartsheet_client.Webhooks.get_webhook(webhook_id)
            logging.info("Webhook retrieved successfully")
            return webhook
        except Exception as e:
            logging.error(f"Error retrieving webhook: {e}")
            return None
    
    
    

    And the fastapi file


    # main.py
    
    
    from fastapi import FastAPI, Request
    from smartsheet_helpers import create_webhook, get_column_values, sort_values, update_dropdown_list
    from config import SMARTSHEET_API_KEY, PRIMARY_SHEET_ID, SECONDARY_SHEET_ID
    from fastapi import FastAPI, Header, HTTPException
    import uvicorn
    import threading
    
    
    # Initialize FastAPI App
    app = FastAPI()
    
    
    @app.post("/webhook")
    async def handle_webhook(challenge: str, webhookId: int, smartsheet_hook_challenge: str = Header(None)):
        # Verify that the received challenge matches the value in the header
        # if challenge != smartsheet_hook_challenge:
        #     raise HTTPException(status_code=400, detail="Invalid challenge")
        print(challenge)
        print(webhook_id)
    
    
        # Construct the response containing the same challenge value
        response_body = {"smartsheetHookResponse": challenge}
    
    
        # Return the constructed response with a 200 status code
        return response_body
    
    
    def run_uvicorn():
        uvicorn.run(app, host="0.0.0.0", port=8000)
    
    
    if __name__ == "__main__":
        callback_url = "https://literate-xylophone-v6vr9pxjx5jrh6ppw-8000.app.github.dev/webhook"
    
    
        uvicorn_thread = threading.Thread(target=run_uvicorn)
        uvicorn_thread.start()
        
        # Your code continues here while the server is running in the background
        create_webhook(SECONDARY_SHEET_ID, callback_url)
      
    
    
    
  • Lee Joramo
    Lee Joramo ✭✭✭✭✭✭
    Options

    And as I stated, Smartsheet is getting a 404

    The Smartsheet JSON responses include the Status that the webhook is disabled because the verification failed:

    "status": "DISABLED_VERIFICATION_FAILED"

    and that the verification failed because the request to the URL https://literate-xylophone-v6vr9pxjx5jrh6ppw-8000.app.github.dev/webhook returned a 404

    "disabledDetails": "Request returned HTTP status code 404 (ref id: youhoi)"

    There are many reasons this could be happening related to GitHub Codespaces or your code. If your code is not returning a 404, then it is most likely Github Codespaces.

    I don't know if your program is running at the moment, but if I go to https://literate-xylophone-v6vr9pxjx5jrh6ppw-8000.app.github.dev/webhook in my browser, I get a 404.

  • Puneet_Bajaj_IITM
    Answer ✓
    Options

    Ya, i tried using ngrook and it worked, was in problem since 3 days. I think smarsheet doesnt support github codespace urls .

    Thanks for your Time and Effort, I appreciate this community and the effort you have put in for me.