Webhooks

The Webhook API enables integrators to subscribe to specific events on the Synctera platform. Additionally, this API helps integrators reduce the number of request calls to the Synctera platform for resource checks. For example, when a customer swipes a card, the Webhook API, if subscribed, will send a POST request to the predefined URL about the transaction. With the API, Synctera will push updates to the integrator instead of the integrator pulling updates.


Understand Webhooks and Events

Creating a webhook defines what events the integrator wants to subscribe to via the event_types field. The following example shows how to subscribe to account updates and customer events. Use the request body of POST /v0/webhooks to create a webhook. enabled_events defines this webhook should handle the account updated events, and the events related to customer. Once such an event occurs, the Webhook API sends a POST request to https://example.com.

{
  "enabled_events": ["ACCOUNT.UPDATED", "CUSTOMER.*"],
  "is_enabled": true,
  "url": "https://example.com"
}

Most events use the <resource>.[<sub-resource>.]<action> naming convention. For example, ACCOUNT.UPDATED means an account was updated. Resources can have sub-resources, TRANSACTIONS.POSTED.CREATED means to subscribe to changes of the sub-resource TRANSACTION.POSTED. You can also use a wildcard * for convenience purposes. For example, you can use it after a <resource> like CUSTOMER.*, which allows you to receive notifications for all the customer events without explicitly listing all of them. Note that Synctera will continuously add more events, so the webhook will automatically subscribe to any new events added to <resource> and send requests.


Integration Steps

  1. Create a signature secret for request validation
  2. Implement a server to receive webhook requests
  3. Create a webhook for events
  4. Triggering events

1. Create a Signature Secret for Request Validation

Signature secrets enable your applications to validate that Synctera initiated the request. To create a signature secret, call POST /v0/webhooks/secret without the request body; Synctera will respond with the generated secret in the response:

{
  "secret": "{signature_secret_key}"
}

πŸ“˜

You should use the same secret for all webhook requests. You can only create webhooks after creating a secret.

Secret Replacement

You may want to rotate the secret or immediately replace it for security purposes.

To rotate a secret:

  1. Call PUT /v0/webhooks/secret to deprecate the old secret and generate a new one. Set the is_rolling_secret field to true to generate the new secret without deleting the old secret right away.
{
  "is_rolling_secret": true
}

πŸ“˜

The request deletes the last secret in 24 hours, so you will have time to update it.

If you want to delete the last secret immediately without waiting for 24 hours, call DELETE /v0/webhooks/secret?old_secret_only=true

2. Implement a Server to Receive Webhook Requests

The applications that receive webhook requests should be publicly accessible so the Synctera platform can send the webhook request to the URL defined in the webhook resource.

Request Headers

The request uses the POST method and contains the following headers:

  • Synctera-Signature - This is the signature of the request. Use the signature secret generated from step 2 to verify the request body.
  • Request-Timestamp - Includes the timestamp when Synctera platform sent the request, which is UNIX time in seconds.
  • Content-Type - Has the value application/json.

Example Request Body

{
  "id": "8145af83-0423-488f-8799-1c8c0bf8b189",
  "url": "http://example.com",
  "webhook_id": "873f7f9c-3063-4098-adcf-1f3af25286f8",
  "type": "ACCOUNT.UPDATED",
  "event_time": "2022-01-25T11:45:54.485698-05:00",
  "metadata": "test webhook",
  "event_resource": "{\"id\":\"7fef9ad7-67dc-4af0-9ce2-70011131c20c\", \"status\": \"ACTIVE_OR_DISBURSED\" ... }",
  "event_resource_changed_fields": "{\"status\": \"RESTRICTED\"}"
}
  • id - The current event ID
  • url - The URL that you specified in your webhook, also the endpoint you will receive this request.
  • webhook_id - The ID of the webhook that sends out this request
  • type- The event type
  • metadata - The same value as the metadata defines in the webhook
  • event_resource Escaped JSON string representing the <resource> of the event. If the type has <sub-resource>, then the string represents the sub-resource.
  • event_resource_changed_fields Escaped JSON string representing the fields that have been updated by the event, which value is prior to the event. Only update event includes this field.

Resource JSON string example

  • before-change object: {"a": 1, "b": 2, "c": 3}
  • after-change object: {"a": 4, "c": 3, "d": 5}

event_resource is just the after-change object itself

event_resource_changed_fields is {"a": 1, "b": 2, "d": null} because:

  • a has value changed, value in before-change object is 1
  • b is deleted from the object, value in before-change object is 2
  • c is not changed, so it is not included in event_resource_changed_fields
  • d is added to the object, value is null because before-change object does not have it

Therefore, event_resource_changed_fields from the original request body means the account resource has been updated. The account status was changed from RESTRICTED to ACTIVE_OR_DISBURSED.

Request Validation

The Synctera-Signature is generated via HMAC with SHA256 hash and the signature secret as the key. The expected value of the Synctera-Signature is HMAC256({request_timestamp} + '.' + {request_body}, {signature_secret_key}). Your service should validate the header matches what you expect.

Furthermore, Synctera-Signature may include two signature strings delimited by . during the rolling secret period, which the old and new signature secrets generate.

⚠️

To prevent replay attacks, integrators should check that the request time is within 5 minutes of now()

See the example code below in Go to handle signature validation:

func ValidateSignature(secret string, payload []byte, reqTime string, signature string) error {
    // Parse request time
    reqTimeVal, err := strconv.ParseInt(reqTime, 10, 64)
    if err != nil {
        return errors.Wrapf(err, "invalid timestamp")
    }

    // Generate the signature
    mac := hmac.New(sha256.New, []byte(secret))
    if _, err := mac.Write([]byte(reqTime + ".")); err != nil {
        return errors.Wrap(err, "failed to write HAMC for reqTime")
    }
    if _, err := mac.Write(payload); err != nil {
        return errors.Wrap(err, "failed to write HAMC for payload")
    }
    generatedSignature := hex.EncodeToString(mac.Sum(nil))

    // Verifiy with the signature header
    for _, curSign := range strings.Split(signature, ".") {
        if generatedSignature != curSign {
            continue
        }
        reqT := time.Unix(reqTimeVal, 0)
        if !reqT.Add(time.Minute * 5).After(time.Now()) {
            return errors.Errorf("signature expired")
        }
        return nil
    }
    return errors.Errorf("invalid signature")
}

Response

The Webhook API expects an HTTP 200 response to indicate that the application processed the request successfully. Any 4xx or 5xx level code will be considered a failure on the application side. The Webhook API will retry the same request with exponential backoff until a successful response or 7 days have passed.

Request timeout

Webhook requests will timeout after 5 seconds. Should a webhook request timeout, Synctera will automatically retry with exponential back off.

3. Create a Webhook Subscription for Events

Call the POST /v0/webhooks endpoint to create a webhook subscription. See below for an example of the request body:

{
  "url": "http://localhost:8080/webhook",
  "description": "random test",
  "enabled_events": ["ACCOUNT.*", "ACCOUNT.CREATED"],
  "metadata": "nothing",
  "is_enabled": true
}
  • url is the endpoint that the webhook sends the request to
  • description is the integrator provided a description of this webhook
  • enabled_events is the list of events that the webhook should subscribe to
  • metadata is the integrator provided a string, which will be copied into every request body as the field metadata
  • is_enabled means the webhook should subscribe to those events. Webhooks will ignore the events when it is set to false

Once you create the webhook, it will send requests for any newly triggered events.

πŸ“˜

Webhook requests are not guaranteed to be real-time calls. In most cases Synctera will send a webhook immediately after an event occurs in most. However, in rare cases the delay could be 15-30 seconds on average. Your app should rely on synchronous responses for time sensitive operations.

4. Triggering Events

We highly recommend testing your application to ensure it can receive the webhook request. There are several useful endpoints to test if the application is working correctly.

  • POST /v0/webhooks/trigger fires a mock event on the Synctera platform, which triggers all the webhooks that match the event (specified in the request body) to send out a request. This will help debug the application without having to CRUD on the actual resource. However, note that the webhook response body will NOT contain event_resource in this case.
  • GET /v0/webhooks/<webhook_id>/events/<event_id> returns the event with the history of the request sent, including those failed attempts with the response body.
  • POST /v0/webhooks/<webhook_id>/events/<event_id>/resend will trigger it to send the webhook request with the same event again, without waiting for the next automatic retry attempt.

Did this page help you?