Submitting data from Jotforms to Anvil

What I’m trying to do:
I have a form in Jotforms that I want to use on a website. I want the submitted data to come to anvils database. Not very familiar with API and creating endpoints and so on.

What I’ve tried and what’s not working:
I want to send the information name, email, phone_number to my database called leads.


@anvil.server.http_endpoint("/moving-leads", methods=["POST"])
def receive_leads(name, email, phone_number):
  app_tables.leads.add_row(Name=name,Email=email,Phone_Number=phone_number)

I checked the logfiles on the server and got this error:

TypeError: receive_leads() missing 3 required positional arguments: 'name', 'email', and 'phone_number'
at /home/anvil/downlink/anvil/_server.py, line 1570
called from /home/anvil/downlink/anvil/_threaded_server.py, line 169

I used webhook to try to send it. What am I missing?

Can you share the client code that’s trying to call the http endpoint? that’s where the issue is.

hmm… I have no client side code… Maybe that is what I am missing? In general, how would you set this up to get form info from a 3rd party like Jotforms/typeforms, etc… What would the client-side code look like?

Right you don’t have anvil client side code, but what code in the jotform is sending to the http endpoint? How is that happening?

It’s obvious from the error that whatever is pinging the endpoint isn’t sending all the arguments you want/need.

Before Anvil, I tried to use JotForms to collect highly detailed data. (It wasn’t a good fit for my use, but Anvil was. I haven’t touched JotForm since.) Significantly, for this post, the data I downloaded was always in JSON format.

Is it possible that JotForms is POSTing your data as a single value in JSON format? Perhaps wrapped in some metadata?

1 Like

My understanding is that arguments to endpoints must come from placeholders in the URL. You don’t have any placeholders in your URL, so you shouldn’t have any arguments to your endpoint. Instead, if your data is being passed in JSON, use anvil.server.request.body_json inside your endpoint to get to that.

I cannot seem to figure out how jotform is sending it. It has a integration option and I chose webhook but not sure.

I believe I set this up correctly, please review

@anvil.server.http_endpoint("/moving-leads", methods=["POST"])
def receive_leads():
  print(anvil.server.request.body_json)
  #app_tables.leads.add_row(Name=name,Email=email,Phone_Number=phone_number)

When I checked logged files

Which if I understood correctly, nothing is coming from Jotforms is that correct? The question then might be from jotforms side am I understand this correctly?

In general, how would you do set up to receive data to your datatable?

Perhaps nothing is coming in JSON format in a way that Anvil interprets as and converts to JSON.

Try to print other properties of the request and see if you find the JSON value as a plain text, so you can convert it.

In some of my HTTP endpoints I have this, which works when the caller doesn’t setup the request the way Anvil expects it (but doesn’t mean it will work for you):

    bb = anvil.server.request.body.get_bytes()
    data = json.loads(bb)
JSONDecodeError: Expecting value: line 1 column 1 (char 0)
at /usr/local/lib/python3.10/json/decoder.py, line 355
called from /usr/local/lib/python3.10/json/decoder.py, line 337
called from /usr/local/lib/python3.10/json/__init__.py, line 346
called from lead_receiving, line 20
called from /home/anvil/downlink/anvil/_server.py, line 1570
called from /home/anvil/downlink/anvil/_threaded_server.py, line 169**strong text**

I do not think it worked :frowning:

Try to print it instead of jsonloading it…

I got this which I have zero understanding of.

b'--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="action"\r\n\r\n\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="webhookURL"\r\n\r\nhttps://slymove.anvil.app/_/api/moving-leads\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="username"\r\n\r\nToshapps\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="formID"\r\n\r\n221664940379060\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="type"\r\n\r\nWEB\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="customParams"\r\n\r\n\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="product"\r\n\r\n\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="formTitle"\r\n\r\nForm\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="customTitle"\r\n\r\n\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="submissionID"\r\n\r\n6101963027558718998\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="event"\r\n\r\n\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="documentID"\r\n\r\n\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="teamID"\r\n\r\n\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="subject"\r\n\r\n\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="isSilent"\r\n\r\n\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="customBody"\r\n\r\n\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="rawRequest"\r\n\r\n{"slug":"submit\\/221664940379060","jsExecutionTracker":"build-date-1734387100723=>init-started:1734387101002=>validator-called:1734387101005=>validator-mounted-false:1734387101006=>init-complete:1734387101008=>onsubmit-fired:1734387102591=>observerSubmitHandler_received-submit-event:1734387102591=>submit-validation-passed:1734387102593=>observerSubmitHandler_validation-passed-submitting-form:1734387102594","submitSource":"form","buildDate":"1734387100723","uploadServerUrl":"https:\\/\\/upload.jotform.com\\/upload","eventObserver":"1","q9_name":"Theo","q7_email":"Mii@gmail.com","q8_phoneNumber":{"full":"(444) 444-4444"},"event_id":"1734387101002_221664940379060_laHv4zN","timeToSubmit":"1","validatedNewRequiredFieldIDs":"{\\"new\\":1}","path":"\\/submit\\/221664940379060"}\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="fromTable"\r\n\r\n\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="appID"\r\n\r\n\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="pretty"\r\n\r\nname:Theo, email:Mii@gmail.com, phone_number:(444) 444-4444\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="unread"\r\n\r\n\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="parent"\r\n\r\n\r\n--------------------------yJIodhLwkkbJqpYWP6idN6\r\nContent-Disposition: form-data; name="ip"\r\n\r\n172.119.155.57\r\n--------------------------yJIodhLwkkbJqpYWP6idN6--\r\n'


Does this help?

This seems beyond my understanding… Yikes I did not think this would be that complicated.

Neither did I! Ideally Anvil ought to be able to ingest that mess (the technical term is “MIME multipart”, and it’s normally only used for file upload) and do something sensible with it.

In the meantime, a little Googling suggests the multipart package. I don’t have a Jotforms account to test with, but something like this might work:

# Make sure you've installed the "multipart" module
from multipart import MultipartParser, parse_options_header
from anvil.server import request

@anvil.server.http_endpoint("/moving-leads", methods=["POST"])
def receive_leads():
  data = request.body.get_bytes()
  # for debugging
  print(request.headers)
  separator = parse_options_header(request.headers["content-type"])
  parser = MultipartParser(BytesIO(data), separator)
  # Now you can loop through all the parts
  for part in parser.parts():
    print(f"{part.name}: {part.value}")
  # Or you can get a particular part
  print(part.get("rawRequest")) # Your sample suggests "rawRequest" gets the whole JSON string - you should be able to parse this!

Got an error.

Starting server runtime... Server runtime connected.
NameError: name 'BytesIO' is not defined
at lead_receiving, line 35
called from /home/anvil/downlink/anvil/_server.py, line 1570
called from /home/anvil/downlink/anvil/_threaded_server.py, line 169
{'newrelic': 'eyJ2IjpbMCwxXSwiZCI6eyJ0eSI6IkFwcCIsImFjIjoiNjkxNjIwIiwiYXAiOiIxMzE0NDY2Njk5IiwiaWQiOiJiZDg4Y2RhMTJlMTc0NTMwIiwidHIiOiI4ZGRiNjQyNzkzZGQzMDg4IiwidHgiOiI4ZGRiNjQyNzkzZGQzMDg4IiwicHIiOjEuNTA0MTAsInNhIjp0cnVlLCJ0aSI6MTczNDM5NTI5OTk4Mn19', 'host': 'slymove.anvil.app', 'content-type': 'multipart/form-data; boundary=------------------------xoQPhMsjNpMKKVEBOaSPmJ', 'cookie': None, 'content-length': '3352', 'accept': '*/*', 'tracestate': '691620@nr=0-0-691620-1314466699-bd88cda12e174530-8ddb642793dd3088-1-1.504100-1734395299982', 'x-forwarded-for': '35.226.242.92', 'traceparent': '00-00000000000000008ddb642793dd3088-bd88cda12e174530-01'}

You’ll likely need to import BytesIO, e.g. from io import BytesIO

1 Like

Still gives an error but a different one.

TypeError: can't concat tuple to bytes
at /home/anvil/.env/lib/python3.10/site-packages/multipart.py, line 323
called from /home/anvil/.env/lib/python3.10/site-packages/multipart.py, line 751
called from /home/anvil/.env/lib/python3.10/site-packages/multipart.py, line 722
called from /home/anvil/.env/lib/python3.10/site-packages/multipart.py, line 729
called from lead_receiving, line 38
called from /home/anvil/downlink/anvil/_server.py, line 1570
called from /home/anvil/downlink/anvil/_threaded_server.py, line 169

@jshaffstall Is it generally this complicated to send and receive via API or is it just JotForm’s? haha like I am not married to JotForm’s is the thought that comes to mind. I can use any number of services. I am actually more in favor of Typeform.

Honestly, unless your jotform Form UI is super complicated, I’d say just rewrite it as an anvil app and convert your http server function to an anvil server callable.

Making a simple interest form in Anvil UI is super easy with the drag and drop editor imho.

I can think of one called Anvil.

1 Like