Skip to main content
Follow these steps to integrate webhooks:
1

Create an HTTP endpoint to receive webhook requests.

This endpoint must accept POST requests such as the one below:
  • URL: https://website.com/webhook_payment (HTTPS is mandatory)
  • METHOD: POST
  • HEADERS:
  • BODY:
This is an example of a webhook sent for a payment_intent.created event. The data field holds the object concerned by the event, owner is your merchant ID and id identifies the event.
2

Register this endpoint with the HUB2 API and subscribe to events.

The HUB2 API provides a dedicated endpoint for this action. Call it with the following body:
Code samples:
The webhook is created in the environment (live or sandbox) given in the Environment header. The HUB2 API responds with HTTP 201 and a body such as:
This response contains the secret associated with the webhook: "secret": "whsec_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX". It is returned only once, at creation.
Store the secret securely: you need it to verify the integrity of every webhook you receive.For more details, see the webhooks API reference: Create a webhook.
3

Verify the webhook signature.

This step is mandatory: it ensures that no one can send you forged webhooks.Skipping it leaves a vulnerability that a malicious party could exploit, for example to have their own transactions validated.
To make sure a webhook comes from the HUB2 API, check the signature of its body (payload).

a. Get the secret of the webhook.

This secret was returned in step 2 ("secret": "whsec_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" in the example).

b. Sign the payload.

Read t, the Unix timestamp in seconds, from the Hub2-Signature-V2 header (see step c). Take the body of the POST request sent by HUB2 (see step 1) as a string, exactly as received, and compute the HMAC-SHA256 of the string <t>.<raw body> with the secret as key, encoded in lowercase hexadecimal.Examples by programming language:
Sign the raw body as received, before any parsing. Parsing the JSON and serializing it again can change the string (spacing, key order, character escaping) and produce a different signature.

c. Check the signature.

Finally, compare the computed signature with the one sent by HUB2 in the request headers:
  • Hub2-Signature-V2: header name (HTTP header names are case-insensitive).
  • t: Unix timestamp in seconds, the one you signed in step b.
  • v1: signature computed with the current secret.
  • v0: signature computed with the previous secret during the 24 hours following a rotation. Otherwise, v0 is omitted.
Accept the webhook only if t is no more than 5 minutes away from your current time and your signature matches v1, or v0 during a secret rotation. Use a constant-time comparison function where your language provides one. Retries and replays carry a new t but the same event id: deduplicate on the id.
Each request also carries the legacy Hub2-Signature: s1=<signature>,s0=<signature> header, where s1 and s0 are the HMAC-SHA256 of the raw body alone, with the current and previous secret (s0 is empty outside a rotation). It is kept for existing integrations, but it has no timestamp and does not protect against replayed requests: use Hub2-Signature-V2.
When the secret is rotated (from the HUB2 dashboard), the previous secret keeps signing deliveries (v0, and s0 in the legacy Hub2-Signature) for 24 hours. Then only the new secret is used. Switch your endpoint to the new secret within that window.