Plastiq Connect uses webhooks to notify clients about asynchronous API lifecycle events. Plastiq Connect currently supports webhook events for Payments.

When and Why to Use Webhooks

Webhooks are particularly useful when an integrating partner needs to respond to an asynchronous event that occurs in the Plastiq Connect system.

For example, an integrating partner may want to be notified when a Payment was successfully deposited into the Recipient's bank account, so that the partner's Payer can be notified.

Supported Events

EventEvent ObjectDescription
payment.state_changePAYMENTAll state change events pertaining to a Payment. See Payment States for a current list of payment states.
document.requestDOCUMENTWebhook event to request additional information.
payment.disbursement_informationPAYMENTWebhook event with additional disbursement account information including routing number, account last four, and bank name.
payment.trace_informationPAYMENTWebhook event with additional trace information.

How Connect Webhooks Work

Plastiq Connect will emit a webhook notification for any event that an integrating partner subscribes to as soon as it occurs in our system.

Acknowledging Requests

Given a valid and secure (HTTPS) URL, Plastiq Connect will dispatch an event via an HTTP call.

Subscribers, typically the integrating partner, will have up to 5 seconds to acknowledge that they have received the webhook payload by returning an HTTP status code of 200.

Webhook Payload Format

All Plastiq Connect webhook events are emitted with a consistent request payload, which contains Ids and associated non-sensitive information.

πŸ“˜

Data Sensitivity

No Personal Identifiable Information (PII) or Payment Card Industry Data Security Standard (PCI DSS) information will ever be sent via Plastiq Connect to a webhook subscriber.

Sample Request Payload with Field Descriptions

{
  "id": "0a176a3a-1912-4da0-88b0-131d8be60636",
  "event": "payment.state_change",
  "data": {
    "id": "37e51171-5f17-4551-8dcd-755666ae7483",
    "object": "PAYMENT",
    "state": "CHARGED",
    "timestamp": 1620080776056,
    "payer": {
      "id": "78c38cde-ab78-4741-b5e6-8164c9edd6e3"
    }
  }
}
{
  "id": "dee9427c-3e33-490b-a9f1-9fd16b75a14f",
  "event": "document.request",
  "data": {
    "id": "fc3d3ad3-52ae-45d7-bae0-501d112547ef",
    "object": "PAYMENT",
    "type": "INVOICE",
    "timestamp": 1620080776056,
    "payer": {
      "id": "78c38cde-ab78-4741-b5e6-8164c9edd6e3"
    }
  }
}
{
  "id": "dc99f29a-bf13-4cf1-beda-3452ac6d7781",
  "event": "payment.disbursement_information",
  "data": {
    "id": "26df60ce-cff9-41e5-ab8f-d35976d95f68",
    "object": "PAYMENT",
    "disbursementInformation": {
      "accountLastFour": "1234",
      "routingNumber": "123456789",
      "bankName": "bankName"
    },
    "timestamp": 1655223000158,
    "payer": {
      "id": "3bd80c82-643f-40ed-b262-49cbf714f232"
    }
  }
}
{
  "id": "0a176a3a-1912-4da0-88b0-131d8be60636",
  "event": "payment.trace_information",
  "data": {
    "id": "37e51171-5f17-4551-8dcd-755666ae7483",
    "object": "PAYMENT",
    "traceInformation": {
      "reference": "12345678"
    },
    "timestamp": 1620080776056,
    "payer": {
      "id": "78c38cde-ab78-4741-b5e6-8164c9edd6e3"
    }
  }
}
Webhook payload fieldDescription
idA unique Id (UUID v4) generated specifically for this given event, which can be used as a deduplication key.
eventThe event for which the integrating partner subscribed. See Supported Events above for a list of available event types.
dataData pertaining to the event. This part of the payload may vary for a given event type.
data.idThe id of the resource object the event occurred on.
data.objectThe API resource object type for which the event occurred.
data.stateThe specific state change event that occurred for the object.
data.timestampThe epoch timestamp at which the event was emitted from the Plastiq system.
data.payer.idThe id of the Payer the event was published on behalf of.
data.disbursementInformationThe payment disbursement account information, including routing number, account last four, and bank name.
date.traceInformation.referenceTrace information reference number for the payment.

Retry and Backoff Strategy

If the webhook client fails to acknowledge the event within 5 seconds (maybe because their system is down or overloaded), we will retry several times with an increasing time interval between attempts.

This is done to ensure our clients receive event notifications reliably. The webhook system will attempt to retry a total of 8 times before the event is marked as failed. Plastiq Connect webhooks are built for at-least-once delivery in mind.

❗️

Subscription deactivation

If you fail to acknowledge a webhook after 8 tries, your subscription will be disabled and you will no longer receive future webhook events. This is done to ensure we do not continue to send requests to a non-responsive / inactive client.

Retry Time Intervals

AttemptRetry delay between events
11 minute
25 minutes
315 minutes
41 hour
56 hours
612 hours
71 day
82 days

πŸ“˜

Jitter

All retry delays are subject to random jitter value within +/- 10% of the defined time interval. For example, for retry attempt 4, where the retry delay is 1 hour, you would receive the request between 54 and 66 minutes.

This is minor amount of randomization done to reduce congestion on your system.

Webhook Signatures and Security

How to verify your webhook payload

Plastiq Connect webhooks events are each signed with a unique shared secret only accessible to the integrating partner. This is done to ensure integrating partners can verify Plastiq Connect-originated payloads.

As part of the webhook HTTP request sent to subscribing partners, we include the following request headers:

HeaderDescription
Plastiq-SignatureA header signed (SHA256 HMAC) with the shared secret consisting of: timestamp.request_body - where timestamp is the value of the Plastiq-Timestamp header, request_body is the webhook notification HTTP request's payload, and . is a separator between the two.
Plastiq-TimestampThe epoch timestamp at which the HTTP request containing the event payload was sent. This can be used to prevent replay attacks.

See more about Replay Attacks here.

In order to verify that the request was received from Plastiq, sign a string of: Plastiq-Timestamp + . + JSON representation of HTTP request payload with the shared secret you received when you subscribed to an event type.

Then, compare it to the Plastiq-Signature header value. If they are the same, you can be certain the request was not tampered with.

Below are examples of how to verify a signature:

import hmac, hashlib, json
...
def is_verified_payload(payload: dict, header_signature: str, header_timestamp: str, shared_secret: str):
  h = hmac.new(shared_secret.encode('utf-8'), digestmod=hashlib.sha256)
  hash_string = header_timestamp + '.' + json.dumps(payload, separators=(',', ':'))
  h.update(hash_string.encode('utf-8'))
  signature = h.hexdigest()
  return signature == header_signature
const crypto = require('crypto)

function isVerifiedPayload(sharedSecret, timestamp, payload, signature) {
  const hmac = crypto
  	.createHmac('sha256', sharedSecret)
  	.update(`${timestamp}.${JSON.stringify(payload)}`)
  
  const signedPayload = hmac.digest('hex')

  return signedPayload == signature
}
void validateRequestSignature(String plastiqTimestamp, Object requestBody, String plastiqSignature) throws Exception {
		// stringify body
		ObjectMapper Obj = new ObjectMapper();
		String bodyString = Obj.writeValueAsString(requestBody);
		// generate hmac
		Mac mac = Mac.getInstance(SIGNATURE_ALGORITHM);
		mac.init(new SecretKeySpec(SIGNATURE_KEY.getBytes("UTF-8"), SIGNATURE_ALGORITHM));
		String requestHash = Hex.encodeHexString(mac.doFinal(plastiqTimestamp.concat(".").concat(bodyString).getBytes("UTF-8")));
		// confirm if request matches sent signature
		if (!plastiqSignature.equals(requestHash)) {
			throw new Exception("invalid signature");
		}
	}

Webhook Source IP Range

All webhook event notifications are sent from a defined range of static IP addresses that clients can whitelist for further security.

How to Subscribe to Webhook Events

Webhook subscriptions are currently handled on an ad-hoc basis. Reach out to us to subscribe to webhook events for your integration.

Best Practices

Acknowledge early

Sometimes, processing a webhook event can take time. Given that Plastiq Connect webhook event timeouts will be retried automatically, it is important to acknowledge receipt of an event notification as early as possible to avoid processing the same event.

Acknowledge but ignore events you don't care about

There may be events you have registered for, but simply do not need to process.

An example of such an event may be a specific Payment Status notification that does not need further action.

In these cases, it is important to still return an HTTP 200 response, but simply discard the message to prevent Plastiq Connect from unnecessarily resending you the same event.

Verify event signatures and use a whitelist

Given that webhooks, by nature, require you to expose an endpoint publicly (to some level), it is important to confirm that the event you received is from Plastiq Connect.

To further protect your system from unwanted traffic, whitelisting our domain, outbound.plastiq.com, is suggested.

Reporting

You will be able to query all of your webhooks requests and payloads by timestamp.


What’s Next