Use webhooks to get notifications about events in your app, other apps, or the Wix site:
- Provisioning events – when a user adds or removes your app.
- Billing events – when a user upgrades your app, makes an in-app purchase, requests to cancel your app’s premium plan, or when the premium plan ends.
- Events related to the user’s contacts and site visitors – when a contact is created/updated in the user’s site, and when a site visitor performs an action in the site, like a purchase.
Here’s how to use webhooks in your app:
- Register for webhooks in the Dev Center.
- Receive webhooks when an event occurs. We send an HTTPS POST request to your server URL with the event’s data. Find out more about the request parameters.
- Verify the webhook’s signature, to make sure that the webhook was sent by Wix and not a third party.
We do our best to preserve timeliness and order in events. However, keep the following in mind when using webhooks:
- We can’t guarantee that you’ll receive messages immediately and in the right order. When we detect an event delivery failure, we’ll try to deliver the event to your app three times during the first 24 hours from the time the event occurred. However, you might not receive messages in the order in which they occurred.
- Make sure your app can handle receiving the same event more than once. We store copies of your app events on multiple servers for redundancy and high availability. On rare occasions, one of the servers storing a copy might be unavailable when you receive the event. The copy won’t be deleted from the unavailable server, so you might receive the same event more than once.
Register in the Dev Center to receive events relevant to your app. When an event occurs, we send an HTTPS POST request to your server URL with the event’s data.
Use a server that supports HTTPS.
How to register for billing and provisioning events:
- Go to the Register Your App tab in your app’s page.
- In the App Components area, select Add new Component > Server.
- Enter the HTTPS URL for the server you want to get the webhook.
- Choose the events you want to receive in your app and click Add.
How to register for contact/activity events:
- Go to the WixHive API tab in your app’s page.
Note: You’ll only see this tab once your mockups were approved. You won’t see this tab in test apps or if you’re in the proposal/mockup review stages. - In the Register to get the WixHive events area, enter the HTTPS URL for the server you want to get the webhook.
- Choose the events you want to receive in your app and click Save.
Each webhook is sent to your server endpoint with the following request parameters.
Parameter Type | Name | Value |
---|---|---|
Header | x-wix-application-id | App ID |
Header | x-wix-timestamp | Timestamp in UTC format (ISO 8601), for example: |
Header | x-wix-signature | HMACSHA-256 signature, generated using the |
Header | x-wix-instance-id | The App Instance ID - a unique id that identifies the app in a specific site |
Header | x-wix-event-type | Event type |
Header | x-wix-event-id | Event ID |
Header | content-type | application/json |
Body | - | Event data, sent in JSON format |
We send every POST request with a digital signature in the x-wix-signature header to signify that the calling party is indeed Wix.
Check the signature you received is from Wix:
- Sort all request parameters by parameter name (in ascending alphanumeric order) and concatenate their values with only a line break (n) as a separator. The request parameters include the following:
a. Query parameters, all except for the signature parameter
b. These headers with the x-wix- prefix: application-id, instance-id, event-type, timestamp, and event-id. For parameters with multiple values, trim and concatenate values with a comma (,).
- Concatenate, in the following order, and separate by a line break (n)
a. HTTP method, converted to uppercase
b. All values from the sorted request parameters (the output of #1)
c. The request body
- Compute the HMACSHA-256 of the combined information using your app secret key.
- Encode the hash to a Base64 string.
- Compare the encoded string to the x-wix-signature header.
Code samples:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | public boolean isFromWix(HttpServletRequest request, String requestBody) { // get signature header String wixSignature = request.getHeader(WIX_SIGNATURE_HEADER); if (wixSignature == null) { return false; } // All headers with the prefix x-wix-*, except the x-wix-signature header Enumeration headerNames = request.getHeaderNames(); SortedSet<String> wixHeaders = new TreeSet<>(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement().toString(); // All headers with the prefix x-wix-*, except the x-wix-signature header if (headerName.startsWith(WIX_HEADER_PREFIX) && !WIX_SIGNATURE_HEADER.equals(headerName) ) { wixHeaders.add(headerName); } } /** * Concatenate, in the following order, separated by a line break: 'n' * 1. HTTP Method, converted to upper case * 2. All values from the sorted request parameters (the output of #1) * 3. For PUT or POST requests : the request body. */ StringBuilder callData = new StringBuilder(1024); // 2.1 HTTP Method, converted to upper case callData.append(request.getMethod().toUpperCase()).append('n'); // 2.2 All values from the sorted request parameters (the output of #1) for (String header : wixHeaders) { callData.append(request.getHeader(header)).append('n'); } // 2.3 For PUT or POST requests : the request body. callData.append(requestBody); // Step 3 - Compute the HMACSHA-256 of the combined information using your app secret key // Step 4 - Encode the hash to a base64 string String signed = sign(callData.toString()); // Step 5 - Compare the encoded string to the x-wix-signature header return signed.equals(padBase64String(wixSignature)); } private String sign(String data) { byte[] signature = null; try { String secret = "<YOUR APP SECRET>"; Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256"); sha256_HMAC.init(secret_key); signature = sha256_HMAC.doFinal(data.getBytes(CharEncoding.US_ASCII)); } catch (Exception e){ System.out.println("Error.."); } return Base64.encodeBase64String(signature); } private String padBase64String(String data) { if (data.length() % 4 > 0) data = StringUtils.rightPad(data, data.length() + 4 - data.length() % 4, '='); return data.replace('_', '/').replace('-', '+'); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | public boolean isFromWix(HttpActionContext actionContext) { var request = actionContext.Request; string signature = ExtractHeaderValue(request, "x-wix-signature"); if (signature == null) { return false; } var signatureBuilder = new List<string>(); /** * Concatenate, in the following order, separated by a line break: 'n' * 1. HTTP Method, converted to upper case * 2. All values from the sorted request parameters * 3. For PUT or POST requests : the request body. */ // 1. HTTP Method, converted to upper case signatureBuilder.Add(request.Method.Method.ToUpperInvariant().Trim()); // 2. All headers with the prefix x-wix-*, except the x-wix-signature header var parameters = new SortedDictionary<string, string>(); foreach (var key in request.Headers.Select(item => item.Key)) { if (key.StartsWith("x-wix-") && key != "x-wix-signature") { parameters[key] = string.Join(",", request.Headers.GetValues(key).Select(value => value.Trim())); } } signatureBuilder.AddRange(parameters.Select(item => item.Value)); // 3. For PUT or POST requests : the request body if (request.Content.Headers.ContentType != null && request.Content.Headers.ContentType.MediaType != "x-www-form-urlencoded") { string documentContents = request.Content.ReadAsStringAsync().Result; signatureBuilder.Add(documentContents.Trim()); } var mySignature = string.Join("n", signatureBuilder); try { using (var crypt = new HMACSHA256(Encoding.ASCII.GetBytes(SecretKey))) { var signature = PadBase64String(signedHeaders); // Compute the HMACSHA-256 of the combined information using your app secret key byte[] mySig = crypt.ComputeHash(Encoding.ASCII.GetBytes(headers)); // Encode the hash to a base64 string var mySignature = Convert.ToBase64String(mySig); // Compare the encoded string to the x-wix-signature header return (mySignature == signature); } } catch (Exception) { // Failed to decode signature return false; } } private static string PadBase64String(string data) { if (data.Length % 4 > 0) data = data.PadRight(data.Length + 4 - data.Length % 4, '='); return data.Replace("_", "/").Replace("-", "+"); } private static string ExtractHeaderValue(HttpRequestMessage request, string header) { IEnumerable<string> values; if (request.Headers.TryGetValues(header, out values)) { return values.First(); } return null; } |
You can receive these provisioning events:
- /provision/provision – a user added your app
- /provision/disabled – a user removed your app
Webhook/provision/provision
This event is posted when the app is added to a website. If the website was never saved, the event will be sent only after the user saves the website.
Webhook Data:
1 2 3 4 5 6 7 8 9 10 11 12 13 | POST /webhook-url x-wix-application-id: << guid >> x-wix-timestamp: << timestamp of the webhook request >> x-wix-signature: << signature of the webhook request >> x-wix-instance-id: << guid >> x-wix-event-type: /provision/provision x-wix-event-id: << guid >> content-type: application/json { “instance-id” (string): << A unique id that identifies the app in a specific site. >> “origin-instance-id” (string, returned if the site was copied): <<The instance ID of the app in the original website. >> } |
Webhook/provision/disabled
This event is posted when all components of the app are removed from a website. If the website has never been saved, the event will be sent only after the user saves the website.
Webhook Data:
1 2 3 4 5 6 7 8 9 10 11 12 | POST /callback-url x-wix-application-id: << guid >> x-wix-timestamp: << timestamp of the webhook request >> x-wix-signature: << signature of the webhook request >> x-wix-instance-id: << guid >> x-wix-event-type: /provision/disabled x-wix-event-id: << guid >> content-type: application/json { “instance-id” (string): << A unique id that identifies the app in a specific site. >> “origin-instance-id” (string, returned if the site was copied): <<The instance ID of the app in the original website. >> } |
You can receive these billing events:
- /billing/upgrade – a user upgraded your app or made an in-app purchase
- /billing/statuschanged – a change in the user’s status relating to premium and in-app purchases
- /billing/cancel – a user cancelled your app’s premium plan
If you register for /billing/statuschanged, you’ll receive cancel and upgrade events – so there’s no need to also register for /billing/upgrade and /billing/cancel. Check the event-type field to know which event occurred.
Webhook/billing/upgrade
This event is posted when a site owner purchases a premium package of the app, or makes an in-app purchase.
To check if a user upgraded your app, use the app instance parameter – here’s how. (Don’t rely only on the webhook!)
If you registered for the /billing/statuschanged event, you’re already set up to receive the upgrade event – so there’s no need to also register for /billing/upgrade.
Webhook Data:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | POST /webhook-url x-wix-application-id: << guid >> x-wix-timestamp: << timestamp of the webhook request >> x-wix-signature: << signature of the webhook request >> x-wix-instance-id: << guid >> x-wix-event-type: /billing/upgrade x-wix-event-id: << guid >> content-type: application/json { “vendorProductId” (string): <<The package ID of the premium plan, as specified in the Dev Center. >> “cycle” (string, optional): <<The payment cycle for this premium plan - 'MONTHLY', 'YEARLY' or 'ONE_TIME'.>> “occurredAt” (datetime): <<The time of the event, as an ISO 8601 timestamp.>> “funnel” (string, optional): <<The funnel that brought the user to purchase the premium plan - 'REGULAR' or 'CART'. >> “coupon” (string, optional): <<The coupon name used to purchase the premium plan. >> “expiresOn” (datetime, optional): << The date on which the premium plan will expire, as an ISO 8601 timestamp. >> } |
Webhook/billing/statuschanged
We report the following subscription changes:
- PURCHASE_IMMEDIATE: A site owner upgraded your app or made an in-app purchase. This event is the same as /billing/upgrade – so if you register for /billing/statuschanged, there’s no need to also register for /billing/upgrade.
- CANCEL_REQUESTED: A site owner requested to cancel the app’s premium plan. The site is entitled to premium services/features until the end of the current billing cycle.
- CANCEL_RESCINDED: After requesting to cancel the app’s premium plan, the site owner now cancelled the request – and will keep the premium plan.
- CANCEL_IMMEDIATE: The site is no longer using this premium plan. This event is the same as /billing/cancel – so if you register for /billing/statuschanged, there’s no need to also register for /billing/cancel.
Important: This event is not sent when a user’s in-app purchase credit ends – you’ll need to keep track of the user’s balance.
Make sure to only change a user’s subscription when your app receives PURCHASE_IMMEDIATE and CANCEL_IMMEDIATE. Users who cancel an app’s premium plan are still entitled to premium services, so don’t change their premium status when your app receives CANCEL_REQUESTED and CANCEL_RESCINDED.
Webhook Data:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | POST /webhook-url x-wix-application-id: << guid >> x-wix-timestamp: << timestamp of the webhook request >> x-wix-signature: << signature of the webhook request >> x-wix-instance-id: << guid >> x-wix-event-type: /billing/statuschanged x-wix-event-id: << guid >> content-type: application/json { “event” (string): <<The change that occurred in the user’s premium plan - 'PURCHASE_IMMEDIATE', 'CANCEL_IMMEDIATE', 'CANCEL_REQUESTED', or 'CANCEL_RESCINDED'.>> “occurredAt” (datetime): <<The time of the event, as an ISO 8601 timestamp.>> “reason” (string, optional): <<The reason the premium plan was cancelled - 'PAYMENT' or 'USER_CANCEL'. >> “userReason” (string, optional): <<The user’s reason for cancelling the premium plan. Returned only if reason is USER_CANCEL.>> “vendorProductId” (string): <<The package ID of the premium plan, as specified in the Dev Center. >> “prevVendorProductId” (string, optional): <<If the user changed the premium plan, returns the package ID of the previous plan (as specified in the Dev Center).>> “cycle” (string, optional): <<The payment cycle for this premium plan - 'MONTHLY' or 'YEARLY' or 'ONE_TIME'.>> “prevCycle” (string, optional) <<The previous payment cycle, if relevant - 'MONTHLY', 'YEARLY', or 'ONE_TIME'. >> “funnel” (string, optional): <<The funnel that brought the user to purchase the premium plan - either 'REGULAR' or 'CART'. >> “coupon” (string, optional): <<The coupon name used to purchase the premium plan. >> “expiresOn” (datetime, optional): <<The date on which the premium plan will expire, as an ISO 8601 timestamp.>> } |
Webhook/billing/cancel
This event is posted when the app’s premium plan ends due to user cancellation or billing system cancellation.
If you registered for the /billing/statuschanged event, you’re already set up to receive the cancel event – so there’s no need to also register for /billing/cancel.
Webhook Data:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | POST /callback-url x-wix-application-id: << guid >> x-wix-timestamp: << timestamp of the webhook request >> x-wix-signature: << signature of the webhook request >> x-wix-instance-id: << guid >> x-wix-event-type: /billing/cancel x-wix-event-id: << guid >> content-type: application/json { “vendorProductId” (string): <<The package ID of the premium plan, as specified in the Dev Center. >> “cycle” (string, optional): <<The payment cycle for this premium plan - 'MONTHLY' or 'YEARLY' or 'ONE_TIME'.>> “occurredAt” (datetime): <<The time of the event, as an ISO 8601 timestamp.>> “reason” (string, optional): <<The reason the premium plan was cancelled - 'PAYMENT' or 'USER_CANCEL'. >> “userReason” (string, optional): <<The user’s reason for cancelling the premium plan. Returned only if reason is USER_CANCEL.>> } |
You can receive these events:
- /contacts/created – a contact was created in the user’s site
- /contacts/updated – a contact was updated in the user’s site
- /activities/posted – a site visitor performed an activity in the user’s site
Need more details about these events? You can use the WixHive platform to get more information.
Webhook/contacts/created
Webhook Data:
1 2 3 4 5 6 7 8 9 10 11 | POST /webhook-url x-wix-application-id: << guid >> x-wix-timestamp: << timestamp of the webhook request >> x-wix-signature: << signature of the webhook request >> x-wix-instance-id: << guid >> x-wix-event-type: /contacts/created x-wix-event-id: << guid >> content-type: application/json { "contactId": <<A unique GUID to identify the contact.>> } |
Webhook/contacts/updated
This event is posted when a Contact is updated by any app installed in the website.
Webhook Data:
1 2 3 4 5 6 7 8 9 10 11 | POST /webhook-url x-wix-application-id: << guid >> x-wix-timestamp: << timestamp of the webhook request >> x-wix-signature: << signature of the webhook request >> x-wix-instance-id: << guid >> x-wix-event-type: /contacts/updated x-wix-event-id: << guid >> content-type: application/json { "contactId": <<A unique GUID to identify the contact.>> } |
Webhook/activities/posted
This event is fired when a new activity is posted by any app installed in the website.
Webhook Data:
1 2 3 4 5 6 7 8 9 10 11 12 13 | POST /webhook-url x-wix-application-id: << guid >> x-wix-timestamp: << timestamp of the webhook request >> x-wix-signature: << signature of the webhook request >> x-wix-instance-id: << guid >> x-wix-event-type: /activities/posted x-wix-event-id: << guid >> content-type: application/json { “activityId” (string): <<A GUID to identify the activity. >> “activityType” (string): <<The type of activity.>> "contactId" (string, optional): <<If a contact is associated with this activity, returns the contactId.>> } |