Using Webhooks with Rails - ruby-on-rails

I am unfamiliar with Webhooks but I feel like they are the right thing for my app.
I have the following documentation for FluidSurveys webhook
I understand how I can make the webhook through a POST request to their API, but I don't know how can I tell where the webhook is actually going to send the response.
Can I pass any subscription url I want? e.g. https://www.myapp.com/test and is that where webhook will send the data? Also, after the webhook is created I'm not sure how to ensure my Rails app will receive the response that is initiated.
I assume a controller method that corresponds with the url I provide to the webhook.
If I'm correct on the controller handling the webhook, what would that look like?
Any guidance is appreciated.

Webhooks hook into your app via a callback URL you provide. This is just an action in one of your controllers that responds to POST requests and handles the webhook request. Every time something changes to the remote service, the remote service makes a request to the callback URL you provided, hence triggering the action code.
I'll exemplify with the survey created event. You start by defining a callback action for this event, where you handle the request coming from the webhook. As stated here the webhook responds with the following body:
survey_creator_name=&survey_name=MADE+A+NEW+SURVEY&survey_creator_email=timothy#example.com&survey_url=http%3A%2F%2Fexample.com%2Fsurveys%2Fbob%2Fmade-a-new-survey%2F``
Let's leave the headers for now, they don't contain important information. The available body parameters (survey_creator_name, survey_name etc.) will reflect all details regarding the new survey available on the remote service. So let's write a callback action that handles this request:
class HooksController
def survey_created_callback
# If the body contains the survey_name parameter...
if params[:survery_name].present?
# Create a new Survey object based on the received parameters...
survey = Survey.new(:name => params[:survey_name]
survey.url = params[:survey_url]
survey.creator_email = params[:survey_creator_email]
survey.save!
end
# The webhook doesn't require a response but let's make sure
# we don't send anything
render :nothing => true
end
end
Let's add the route for this (in config/routes.rb):
scope '/hooks', :controller => :hooks do
post :survey_created_callback
end
This will enable the POST /hooks/survey_created_callback route.
Now you'll need to subscribe this callback URL to the Webhooks API. First you'll want to know which hooks are available to you. You do this by placing a GET request at /api/v2/webhooks/. In the response you'll find the event name, survey and collector parameters.
Finally, you subscribe to one of the previously listed hooks by placing a request to the POST /api/v2/webhooks/subscribe/ URL with the following contents:
{
"subscription_url": "http://your-absolute-url.com/hooks/survey_created_callback",
"event": "[EVENT NAME FROM THE HOOKS LIST]",
"survey": "[SURVEY FROM THE HOOKS LIST]",
"collector": "[COLLECTOR FROM THE HOOKS LIST]"
}
The response to this will be a code 201 if the hook was created successfully, or code 409, if a webhook for the same callback URL already exists. Or something else, if this went bad :)
You can now test the hook, by creating a survey on the remote service and then watch it getting replicated in your Rails app.
Hope this helps...

Related

Where did the Shopify webhook for order update go?

I need to subscribe to the order update Shopify webhook. The thing is, is I can't find it! They have changed the way the shopify_app.rb config file is structured. So this is the way it looks now:
config.scope = "read_shipping, write_shipping, read_products, write_products,
read_fulfillments, write_fulfillments, read_orders, write_orders, read_themes, write_themes, orders/updated"
Looking at the Shopify docs it doesn't look like the order update hook exists anymore. According to their own docs
But there is this old doc that does contain the order update hook: https://help.shopify.com/en/api/reference/events/webhook
I put that style into my config.scope line but I got this when I try and install: https://nimb.ws/JH267z
Any ideas?
EDIT: I'm just trying sh*t now.. I tried update_orders and that didn't work: https://nimb.ws/Yun5Us
Scope has nothing to do with Webhook subscriptions. If you subscribe to a webhook like orders/update, then you receive orders at your designated end point when the update occurs.
Scope is more of an authorization tool, for API access to endpoints at Shopify. No matter what you do with scope, it will not add a webhook subscription for you. For that, you call the Webhook endpoint and add the webhook you need.

Webhook handling with background job?

I just wonder how I could handle webhook from third party API in general.
In my case, I need to handle Webhooks from Stripe.
So I use
StripeEvent to handle and listen for the entrance of webhook handlers. It provides an easy to use interface for handling events from Stripe.
The main implementation is:
take the ID from the POSTed event data
stripe doesn't sign events, so to verify by fetching event from Stripe API.
store events (id) and reject IDs that we've seen already to protect against replay attacks.
Everything works so far.
However, let's assume that
handling little complex logic within in webhook hanlder
listening many webhook requests
In this case, I feel I need to consider to use background job.
Best practices in stripe doc
If your webhook script performs complex logic, or makes network calls, it's possible the script would timeout before Stripe sees its complete execution. For that reason, you may want to have your webhook endpoint immediately acknowledge receipt by returning a 2xx HTTP status code, >and then perform the rest of its duties.
Here is my code,
I've just wondered which part I should bundle and enqueue?
StripeEvent.event_retriever = lambda do |params|
return nil if StripeWebhook.exists?(stripe_id: params[:id])
StripeWebhook.create!(stripe_id: params[:id])
return Stripe::Event.construct_from(params.deep_symbolize_keys) if Rails.env.test? # fetching the event from Stripe API
return Stripe::Event.retrieve(params[:id])
end
StripeEvent.configure do |events|
events.subscribe 'invoice.created', InvoiceCreated.new # handling the invoice.created event in service object
events.subscribe 'invoice.payment_succeeded', InvoicePaymentSucceeded.new
...
end
Short answer, just send all of it by serializing the Stripe::Event instance to a string with Marshal::dump, then deserialize back to a Stripe::Event in your background worker with Marshal::load.
We also wanted to process the webhook requests in the background using our delayed_job system, which stores the jobs in a database table and the job arguments in a string column.
To do that we first needed to serialize the Stripe::Event to a string in our StripeEvent.configure block:
# event is an instance of Stripe::Event
serialized_event = Marshal::dump(event)
Then we queue the background job rather than handling it synchronously, passing our serialized event as a string to where it is stored (in our case a database table) to await being processed.
Then our background worker code can deserialize the string it reads back to a Stripe::Event:
event = Marshal::load(serialized_event)

MVC pattern and RoR or where must that code be placed? [duplicate]

This question already has answers here:
Where do API calls go in a ruby on rails MVC framework project?
(3 answers)
Closed 7 years ago.
My task is to take some data from users. The next part is to make permanent requests to an API of a third party site and to process responses from it. I don't know where this part should be placed: model, controller, or module? The final part of the app will send statuses to users' emails.
Processing user input from an HTTP request is usually done in the controller.
Send a request to the rails server including the user input.
The request will be routed to the appropriate controller action. In the controller action, form an HTTP request to an external API and include the user input in the request using something like RestClient.
Finally, you will send an email to the user and include the request statuses by calling the deliver! method on a mailer class.
Example:
class UsersController < ApplicationController
def controller_action
#user_input = params[:query]
# Build the external API request URI.
# Using www.icd10api.com as an example.
url = Addressable::URI.new(
scheme: "http",
host: "www.icd10api.com",
query_values: {code: #user_input, r: "json", desc: "long"})
# Perform the external request and parse the response
resp = JSON.parse(RestClient.get(url.to_s))
# Finally, deliver the email.
UserMailer.statuses_email(resp).deliver!
# Return status code
render status: 200
end
end
You can always refactor your code into a module, but I only do this if it's used in 3+ locations.
If you're using this as more than a demo app, I would refer to the link in Andrew CP Kelley's comment:
Where do API calls go in a ruby on rails MVC framework project?
References:
https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
You also might want to look into concerns if you're using rails 4+:
How to use concerns in Rails 4
I usually wrap it inside a module or a class and place the file into folder
app/models/services/
Here is the folder I place all things are kind of service, e.g. The logic to requests to API and proceed responses from it.

Rails: HTTP Get request from Callback Method (in model)

I want to create a callback in my User model. after a user is created, a callback is initiated to run get_followers to get that users twitter followers (via full contact API).
This is all a bit new to me...
Is this the correct approach putting the request in a callback or should it be in the controller somewhere? And then how do I make the request to the endpoint in rails, and where should I be processing the data that is returned?
EDIT... Is something like this okay?
User.rb
require 'open-uri'
require 'json'
class Customer < ActiveRecord::Base
after_create :get_twitter
private
def get_twitter
source = "url-to-parse.com"
#data = JSON.parse(JSON.load(source))
end
A few things to consider:
The callback will run for every Customer that is created, not just those created in the controller. That may or may not be desirable, depending on your specific needs. For example, you will need to handle this in your tests by mocking out the external API call.
Errors could occur in the callback if the service is down, or if a bad response is returned. You have to decide how to handle those errors.
You should consider having the code in the callback run in a background process rather than in the web request, if it is not required to run immediately. That way errors in the callback will not produce a 500 page, and will improve performance since the response can be returned without waiting for the callback to complete. In such a case the rest of the application must be able to handle a user for whom the callback has not yet completed.

Simple way for async/event driven request Ruby on Rails site

I am trying to implement a simple solution to help with some behavior. Basically I want to create a listener on a particular url, like
localhost:3000/listen
I have a callback with a 3rd party service that is sending JSON as a post request. In my rails routes I have the route setup, to accept a post request to that namespace.
The thing that I want to happen, is for some logic to be run anytime a new post comes in and to run that logic async without any disruption to the normal web service. For example, the post request will contain some data, if the data has a boolean "true", we need to fire off a Rails Mailer. I normally could do this with a simple rails controller action but this is not correct.
Any thoughts on the best approach to handle this? Would this best with a gem like eventmachine? If anyone could give their feedback to implement a simple solution that would be great!
I would look at your background jobs. I am a Sidekiq fan, and popular alternatives are Resque and DelayedJob.
Your controller will receive the response, and schedule it to be performed in the background. That will send out the mail (or whatever you need it to do) asynchronously.
class CallbackController < ApplicationController
def listen_third_party
data = params.slice([:params, :you, :care, :about])
if data[:boolean_field] == true
CallbackMailer.perform_async(data)
end
end
end

Resources