Integrating a Membership Purchase with Gumroad Ping

I’ve been busy building software lately.

One of the main things I’ve been working on is getting Gumroad purchases connected to my web application.

Why Gumroad? Because the people who have supported my book are already there. It allows me to put all of the content and training videos in one place as well.

Achieving an Automated Gumroad Workflow

The workflow is straightforward.

  1. Customer purchases a membership via Gumroad
  2. Gumroad notifies my application via the Gumroad Ping webhook
  3. If the product matches my membership product identifier, then my app saves the user with the email, license key info, and timestamp
  4. User is sent an email invitation to sign up for the web application

Gumroad Ping

Gumroad Ping is a webhook that posts sale date to your application. It includes enough identifying information to trigger a full invitation workflow.

You can set up a ping by visiting https://gumroad.com/settings/advanced in your Gumroad seller dashboard.

I set up a simple Ruby on Rails API to consume the data and secured it with a secret token that i pasted in the provided Ping field.

Below is my GumroadPing model. In it there is an after_create hook that sends an invitation if the sale type is a subscription. I save the license_key and sale_timestamp to the User model and send out the invitation.

# == Schema Information
#
# Table name: gumroad_pings
#
#  id         :bigint           not null, primary key
#  data       :json             not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#
class GumroadPing < ApplicationRecord
  APP_PRODUCT_ID = "MY_PRODUCT_ID==".freeze

  validates :data, presence: true

  after_create_commit :send_invitation, if: :subscription?


  private

  def subscription?
    data.dig("product_id") == APP_PRODUCT_ID
  end

  def send_invitation
    user = User.invite!(
      email: data.dig("email"),
      name: data.dig("full_name"),
      invited_by: User.first)
    apply_license_key_to_user(user)
  end

  def apply_license_key_to_user(user)
    user.update_attributes(
      sale_timestamp: data.dig("sale_timestamp"),
      license_key: data.dig("license_key")
      )
  end
end

The controller is relatively straightforward as well. I validate the token and 401 if it fails, then attempt to save the model, and if that doesn’t work, then I return a 422 with the error message back to Gumroad (not that they care).

# GumroadPingsController
class GumroadPingsController < ApplicationController
  skip_before_action :verify_authenticity_token, :authenticate_user!

  def create
    return gumroad_unauthorized if invalid_gumroad_token?
    @gumroad_ping = GumroadPing.new(data: gumroad_ping_params)
    if @gumroad_ping.save
      render json: {}, status: :created
    else
      render json: @gumroad_ping.errors, status: :unprocessible_entity
    end
  end

  private
    def invalid_gumroad_token?
      params[:token] != Rails.application.credentials.dig(:gumroad, :token)
    end

    def gumroad_unauthorized
      render json: { message: "unauthorized" }, status: :unauthorized
    end

    def gumroad_ping_params
      params.except(:token, :controller, :action)
    end
end

Gumroad Ping Json Structure

This is where I got a bit hung up and wish Gumroad had slightly better documentation. The json is highly variable so in the initial implementations I had to do some experimentation to see exactly what was coming from Gumroad and stored the payload as a json blob in my database.

Here is an anonymized example of the membership sale data structure.

Subscription with no affiliate sales

{
  "seller_id": "A_LONG_STRING==",
  "product_id": "A_LONG_STRING==",
  "product_name": "Dividend Cultivator Web App",
  "permalink": "mHSuk",
  "product_permalink": "https://gum.co/mHSuk",
  "email": "anonymous@gmail.com",
  "price": "1249",
  "currency": "usd",
  "quantity": "1",
  "order_number": "A_STRINGIFIED_INTEGER",
  "sale_id": "A_LONG_STRING==",
  "sale_timestamp": "2020-06-09T18:11:22Z",
  "subscription_id": "kjletlkjeqasdf==",
  "variants": {
    "Tier": "Base Subscription"
  },
  "offer_code": "an_discount_code_you_have_set_up",
  "license_key": "C14341FC-C14341FC-C14341FC-C14341FC",
  "ip_country": "Canada",
  "recurrence": "quarterly",
  "is_gift_receiver_purchase": "false",
  "refunded": "false",
  "resource_name": "sale",
  "disputed": "false",
  "dispute_won": "false"
}

Fin

That’s really it. I’m haven’t found ping events for refunds, cancellations, or other product events, but would be interested if anyone finds them.

Let me know about your experience with it as I found it pretty pleasant to get set up and working once I knew what the data structure was.

Here’s a 50% off link to the dividend investing software if you’re interested in it.

2 thoughts on “Integrating a Membership Purchase with Gumroad Ping

    1. Thanks Andrew. Yes you’re right. It’s a bit more complex than I wanted it to be, but will likely have to go down that route. I think there are several wrapper libraries for it too.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s