Receive Frame events in your webhook endpoint

Why use webhooks

When building Frame integrations, you may want your applications to receive real-time events from your Frame accounts to allow your backend systems to execute corresponding actions.

To enable webhook events, you must register webhook endpoints. Once registered, Frame can push real-time event data to your application's webhook endpoint whenever events occur in your Frame account. Frame sends these webhook events via HTTPS as a JSON payload containing an Event object.

Receiving webhook events is especially useful for tracking asynchronous events such as a customer's bank confirming a payment, a customer disputing a charge, a recurring payment succeeding, or the collection of subscription payments.

Webhook events

Frame generates event data to inform you of activities within your account.

When an event occurs, Frame creates a new Event object. A single API request may trigger the creation of multiple events. For instance, if you create a new subscription for a customer, you will receive both customer.subscription.created and charge_intent.succeeded events.

By registering webhook endpoints in your Frame account, you enable Frame to automatically send Event objects via POST requests to the registered webhook endpoint hosted by your application. Once your webhook endpoint receives an Event, your app can perform backend actions. For example, you might call your shipping provider's API to schedule a shipment after receiving a charge_intent.succeeded event.

Delivery headers

HTTP POST payloads that are delivered to your webhook's configured URL endpoint will contain several special headers:

  • X-Frame-Webhook-Id: The unique identifier of the webhook.
  • X-Frame-Event: The name of the event that triggered the delivery.
  • X-Frame-Signature: This is the HMAC hex digest of the request body, and is generated using the SHA-256 hash function and the secret as the HMAC key.

Example event payload

The following event shows a customer updated.

{
  "id": "787d686b-3f8d-490e-bd90-4a2ab0c5a81f",
  "type": "customer.updated",
  "object": "event",
  "created": 1729161970,
  "livemode": false,
  "data": {
    "id": "a8a3b804-c43d-4b71-8eee-faacc198f7f9",
    "name": "Jonas Schmidt",
    "email": "jonas+schmidt@gmail.com",
    "phone": "+12127405150",
    "object": "customer",
    "created": 1729143862,
    "updated": 1729143862,
    "livemode": false,
    "metadata": [],
    "description": null,
    "billing_address": null,
    "shipping_address": null
  }
}

Event object structure

Review the event object structure to better understand events and the underlying information they provide.

Event type

You receive events for all of the event types your webhook endpoint is listening for in your configuration. Use the received event type to determine what processing your application needs to perform. The data correspoding to each event type varies.

Live and test mode

You might receive both live and test mode event delivery requests to your endpoints. This can happen if you use a single endpoint for both live and test mode. Use the livemode attribute to check whether the object exists in live or test mode, and determine the correct handling for the event.

Why event objects get generated

This table describes different scenarios that trigger generating Event objects.

SOURCETRIGGER
DashboardWhen you call an API by modifying your Frame resources in the Frame Dashboard.
APIWhen a user action in your app or website results in an API call.

Types of events

This is a list of all the types of events we currently send. We may add more at any time, so in developing and maintaining your code, you should not assume that only these types exist.

EventDescription
customer.createdOccurs whenever a new customer is created.
customer.deletedOccurs whenever a customer is deleted.
customer.updatedOccurs whenever a customer is updated.
charge.capturedOccurs whenever a previously uncaptured charge is captured.
charge.pendingOccurs whenever a pending charge is created.
customer.subscription.createdOccurs whenever a customer is signed up for a new plan.
customer.subscription.updatedOccurs whenever a subscription changes.
charge_intent.canceledOccurs when a ChargeIntent is canceled.
charge_intent.createdOccurs when a new ChargeIntent is created.
charge_intent.payment_failedOccurs when a ChargeIntent has failed the attempt to create a payment method or a payment.
charge_intent.requires_actionOccurs when a ChargeIntent transitions to requires_action state (requires_capture, requires_customer, requires_payment_method, requires_3d_secure).
charge_intent.succeededOccurs when a ChargeIntent has successfully completed payment.
payment_link.createdOccurs when a payment link is created.
payment_method.detachedOccurs whenever a payment method is detached from a customer.
payment_method.updatedOccurs whenever a payment method is updated via the PaymentMethod update API.
product.createdOccurs whenever a product is created.
product.updatedOccurs whenever a product is updated.
product.deletedOccurs whenever a product is deleted.

Creating webhooks

You can create webhooks to subscribe to specific events on Frame that occur in a payments, customers, disputes and more.

For more information about the different types of webhooks, see "Types of webhooks."

Use the following steps to register a webhook endpoint in the Developers Dashboard.

  1. Navigate to the Webhooks page.
  2. Click Create Webhook.
  3. Add your webhook endpoint's HTTPS URL in Endpoint URL.
  4. You can add Description, it's optional.
  5. Under "Enabled events", select the webhook events that you want to receive. You should only subscribe to the webhook events that you need.
  6. Click Create webhook.

Validate deliveries

Once your server is configured to receive payloads, it will listen for any delivery that's sent to the endpoint you configured. To ensure that your server only processes webhook deliveries that were sent by Frame and to ensure that the delivery was not tampered with, you should validate the webhook signature before processing the delivery further. This will help you avoid spending server time to process deliveries that are not from Frame and will help avoid man-in-the-middle attacks.

To do this, you need to:

  1. Go to endpoint details page under Webhooks and copy the secret token.
  2. Store the token securely on your server.
  3. Validate incoming webhook payloads against the token, to verify that they are coming from Frame and were not tampered with.

You should store it in a secure location that your server can access. Never hardcode a token into an application or push a token to any repository.

Validating webhook deliveries

Frame will use your secret token to create a hash signature that's sent to you with each payload. The hash signature will appear in each delivery as the value of the X-Frame-Signature header. For more information, see "Webhook events and payloads."

In your code that handles webhook deliveries, you should calculate a hash using your secret token. Then, compare the hash that Frame sent with the expected hash that you calculated, and ensure that they match. For examples showing how to validate the hashes in various programming languages.

There are a few important things to keep in mind when validating webhook payloads:

  • Frame uses an HMAC hex digest to compute the hash.
  • The hash signature always starts with sha256=.
  • The hash signature is generated using your webhook's secret token and the payload contents.
  • If your language and server implementation specifies a character encoding, ensure that you handle the payload as UTF-8.
  • Never use a plain == operator. Instead consider using a method like secure_compare or crypto.timingSafeEqual, which performs a "constant time" string comparison to help mitigate certain timing attacks against regular equality operators, or regular loops in JIT-optimized languages.

Testing the webhook payload validation

You can use the following secret and payload values to verify that your implementation is correct:

  • secret: "secret should always be a secret"
  • payload: "Accept Payments with Frame"

If your implementation is correct, the signatures that you generate should match the following signature values:

  • signature: 45e16042652068e283740769560cdc25d6cc931fa0656027e0e21a278dd3fa00
  • X-Frame-Signature: sha256=45e16042652068e283740769560cdc25d6cc931fa0656027e0e21a278dd3fa00

Examples

You can use your programming language of choice to implement HMAC verification in your code. Following are some examples showing how an implementation might look in various programming languages.

Ruby example

For example, you can define the following verify_signature function:

def verify_signature(payload_body)
  signature = 'sha256=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_TOKEN'], payload_body)
  return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_FRAME_SIGNATURE'])
end

Then you can call it when you receive a webhook payload:

post '/payload' do
  request.body.rewind
  payload_body = request.body.read
  verify_signature(payload_body)
  push = JSON.parse(payload_body)
  "I got some JSON: #{push.inspect}"
end

Retry Schedule

Frame attempts to deliver each webhook message based on a retry schedule with exponential backoff.

The schedule

Each message is attempted based on the following schedule, where each period is started following the failure of the preceding attempt:

  • 5 seconds
  • 5 minutes
  • 10 minutes

If an endpoint is removed or disabled delivery attempts to the endpoint will be disabled as well.

Indicating successful delivery

The way to indicate that a webhook has been processed is by returning a 2xx (status code 200-299) response to the webhook message within a reasonable time-frame 5 seconds in this case. Any other status code, including 3xx redirects are treated as failures.

Failed delivery handling

After the conclusion of the above attempts the message will be marked as Failed for this endpoint.

Manual retries

You can also use Frame dashboard to manually retry each message at any time.

Troubleshooting Tips

There are some common reasons why your webhook endpoint is failing:

Not using the raw payload body

This is the most common issue. When generating the signed content, we use the raw string body of the message payload.

If you convert JSON payloads into strings using methods like stringify, different implementations may produce different string representations of the JSON object, which can lead to discrepancies when verifying the signature. It's crucial to verify the payload exactly as it was sent, byte-for-byte or string-for-string, to ensure accurate verification.

Missing the secret key

From time to time we see people simple using the wrong secret key. Remember that keys are unique to endpoints.

Sending the wrong response codes

When we receive a response with a 2xx status code, we interpret that as a successful delivery even if you indicate a failure in the response payload. Make sure to use the right response status codes so we know when message are supposed to succeed vs fail.