Uploaded images/files Urls don't show in final webhook

Hey all Pickaxers,

I’m hoping someone can help me with an issue I’ve been having.

I’ve created a self-service ticketing system, and I’m able to collect all relevant details, which are sent in a webhook payload to N8N (all working fine).

The issue starts when I start introducing images/files into the mix, I cannot get Pickaxe to release the image URL at the end of the customer’s conversation (webhook). The only way I’ve been able to get this to unlock and pass the URL in the webhook is at the point of upload, and I specifically ask to “send the image to the webhook” for some reason, it seems like the temporary link (URL) doesn’t cache and is not available later. If I prompt and ask for the file URL to be show directly in the chat then this doesn’t provide the actual link and access is denied.

This is really key for my Pickaxe project as I need customers to be able to upload proof of their issues and this needs to be sent in the final payload to our internal customer service team, any help to fix this issue will be greatly appreciated, I have thought of keeping a directory of all uploaded files and tag any upload with “userId” and “conversationId” to track back but this isn’t ideal, I have also tried the environment variables but I can’t get this to work either.

Below is the action code I have been playing about with -

import os

import requests

def able_to_change_n8n_webhook_copy(data: str):

"""

Sends a basic (POST) request to a n8n.io webhook



Args:

    data (string): This is the data that should be sent to n8n.io

Envs:

    N8N_WEBHOOK_URL (string): Live webhook link from n8n.o

"""



\# --- begin replacement from here (line 17) ---

\# Pull envs and safely parse as JSON arrays (or single strings → list)

json_mod = \__import_\_('json')



def \_parse_env_list(name: str):

    raw = os.environ.get(name, "")

    if not raw:

        return \[\]

    try:

        val = json_mod.loads(raw)

        if isinstance(val, list):

            return \[str(x) for x in val if isinstance(x, (str, bytes))\]

        if isinstance(val, str):

            return \[val\]

        return \[\]

    except Exception:

        \# Not JSON? treat as single URL string

        return \[raw\] if isinstance(raw, str) else \[\]



def \_clean_urls(urls):

    \# Minimal normalisation: strip, keep http/https, de-dupe in order

    seen, out = set(), \[\]

    for u in urls:

        s = (u or "").strip()

        if s and s.lower().startswith(([http://](http://), [https://)](https://\))) and s not in seen:

            seen.add(s)

            out.append(s)

    return out



endpoint = os.environ\["N8N_WEBHOOK_URL"\]



doc_urls = \_clean_urls(\_parse_env_list("PICKAXE_END_USER_DOC_URLS"))

raw_doc_urls = \_clean_urls(\_parse_env_list("PICKAXE_END_USER_RAW_DOC_URLS"))

image_urls = \_clean_urls(\_parse_env_list("PICKAXE_END_USER_IMAGE_URLS"))



payload = {

    "data": data,

    "attachments": {

        "documents": doc_urls,

        "raw_documents": raw_doc_urls,

        "images": image_urls,

        "counts": {

            "documents": len(doc_urls),

            "raw_documents": len(raw_doc_urls),

            "images": len(image_urls),

        },

    },

}



try:

    \# Send a POST request to the n8n webhook

    response = [requests.post](https://eur02.safelinks.protection.outlook.com/?url=http%3A%2F%2Frequests.post%2F&data=05%7C02%7Cbthompson%40aurora.co.uk%7Cff1fceb4d594479d2e0708ddffb30c8f%7Cd224ffb47d5843c0b18c28d73ad934a9%7C0%7C0%7C638947866700891461%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=69ipCpFgcJFUHVnPopcNvNEC%2FWBhTJEH9QJH7%2FKHwRE%3D&reserved=0)(

        endpoint,

        json=payload,

        headers={"Content-Type": "application/json"},

        timeout=20

    )

   

    \# Check for a 200 OK

    if response.status_code == 200:

        print("Successfully sent data to n8n.")

       

        \# Try to parse JSON from n8n’s response

        try:

            response_data = response.json()

            print("Response JSON from n8n:", response_data)

        except ValueError:

            \# If the response isn’t valid JSON, just print it as raw text

            print("Response is not JSON. Raw text:", response.text)

   

    else:

        print(

            f"Failed to send data to n8n. "

            f"Status code: {response.status_code}, "

            f"Response: {response.text}"

        )



    \# Example: print or return the status code at the end

    print(f"Response code: {response.status_code}")



except Exception as e:

    print(f"An error occurred: {str(e)}")

\# --- end replacement ---

Successful Webhook -

Failed Webhook

Thanks in advance

Brett

1 Like

@Ned.Malki @ab2308 Sorry to be a pain and directly asking for your help, but I’m really stuck here and I’m not sure if the problem is with my action code / prompt or it’s a limitation of pickaxe. I need the uploaded documents to be stored somewhere for later review. Ideally, I would like the URL to go through a final webhook payload through N8N but I’m happy if you suggest a workaround.

If you need anything from me, let me know.

Any help is greatly appreciated :+1:

1 Like

@Ned.Malki @ab2308 I may have found a workaround. I have used a test pickaxe to mess around and find a way to get this working. I have used the primary prompt to push any uploaded document/file straight to the webhook at the point of upload. This then hits N8N, and I’ve used a respond to the webhook to pull the extracted URL path back into the main chat, which I can then phrase in the final webhook to our service department. I could work with this but, do you know of any other way to simplify?

1 Like

Hey @brett1321 I’d love to help, but currently in the middle of wrapping up production on the next advanced track for the PPA community.

If you don’t get a reply on here by Monday, I can maybe step in and assist. Just wanted to reply to let you know I saw your message.

Also, try posting this on the PPA community on Peer Support to see if anyone can assist.

-Ned

Hi @Ned.Malki sure, no problem at all.

I appreciate you offering to help. No rush, looking forward to seeing the content for the PPA community, sounds great.

Many thanks,

Brett

1 Like

Hey @brett1321

Thanks for the details - this one should be straightforward ’

Why your Final Webhook isn’t showing uploads
Upload URLs aren’t auto-included in the final payload. You need to read the built-in env vars during the run and pass them forward yourself.

What to do
Right after the upload step, read these (they’re JSON strings), parse them, and store them in variables you include in the Final Webhook:

  • PICKAXE_END_USER_DOC_URLS → uploaded file URLs
  • PICKAXE_END_USER_RAW_DOC_URLS → same files as .txt
  • PICKAXE_END_USER_IMAGE_URLS → uploaded image URLs

Tiny Python helper you can drop into an action:

import os

def read_list(name):
    try:
        v = os.getenv(name, "[]")
        return json.loads(v) if isinstance(v, str) else []
    except Exception:
        return []

file_urls = read_list("PICKAXE_END_USER_DOC_URLS")
raw_file_urls = read_list("PICKAXE_END_USER_RAW_DOC_URLS")
image_urls = read_list("PICKAXE_END_USER_IMAGE_URLS")

# expose these three variables to your Final Webhook step
result = {
    "fileUrls": file_urls,
    "rawFileUrls": raw_file_urls,
    "imageUrls": image_urls
}

Then include fileUrls, rawFileUrls, and/or imageUrls in your Final Webhook body. That’s it -your endpoint will receive the actual upload links.

-Ned