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 thesecret
as the HMACkey
.
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.
SOURCE | TRIGGER |
---|---|
Dashboard | When you call an API by modifying your Frame resources in the Frame Dashboard. |
API | When 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.
Event | Description |
---|---|
customer.created | Occurs whenever a new customer is created. |
customer.deleted | Occurs whenever a customer is deleted. |
customer.updated | Occurs whenever a customer is updated. |
charge.captured | Occurs whenever a previously uncaptured charge is captured. |
charge.pending | Occurs whenever a pending charge is created. |
customer.subscription.created | Occurs whenever a customer is signed up for a new plan. |
customer.subscription.updated | Occurs whenever a subscription changes. |
charge_intent.canceled | Occurs when a ChargeIntent is canceled. |
charge_intent.created | Occurs when a new ChargeIntent is created. |
charge_intent.payment_failed | Occurs when a ChargeIntent has failed the attempt to create a payment method or a payment. |
charge_intent.requires_action | Occurs when a ChargeIntent transitions to requires_action state (requires_capture , requires_customer , requires_payment_method , requires_3d_secure ). |
charge_intent.succeeded | Occurs when a ChargeIntent has successfully completed payment. |
payment_link.created | Occurs when a payment link is created. |
payment_method.detached | Occurs whenever a payment method is detached from a customer. |
payment_method.updated | Occurs whenever a payment method is updated via the PaymentMethod update API. |
product.created | Occurs whenever a product is created. |
product.updated | Occurs whenever a product is updated. |
product.deleted | Occurs 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.
- Navigate to the Webhooks page.
- Click Create Webhook.
- Add your webhook endpoint's HTTPS URL in Endpoint URL.
- You can add Description, it's optional.
- Under "Enabled events", select the webhook events that you want to receive. You should only subscribe to the webhook events that you need.
- 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:
- Go to endpoint details page under Webhooks and copy the secret token.
- Store the token securely on your server.
- 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 likesecure_compare
orcrypto.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.