Zepto API

Zepto allows you to make, get and manage payments using nothing but bank accounts.


Zepto allows you to make, get and manage payments using nothing but bank accounts.

It is important to understand that there are 2 main ways Zepto can be used for maximum flexibility:

  1. Between Zepto accounts.

  2. Between a Zepto account and anyone.

Due to the above, certain endpoints and techniques will differ slightly depending on who you are interacting with. You can find more on this in the Making payments and Getting paid guides.

And for all kinds of How To's and Recipes, head on over to our Help Guide.


  • Authentication is performed using OAuth2. See the Get started and Authentication & Authorisation guides for more.

  • All communication is via https and supports only TLSv1.2.

  • Production API: https://api.zeptopayments.com/.

  • Production UI: https://go.zeptopayments.com/.

  • Sandbox API: https://api.sandbox.zeptopayments.com/.

  • Sandbox UI: https://go.sandbox.zeptopayments.com/.

  • Data is sent and received as JSON.

  • Clients should include the Accepts: application/json header in their requests.

  • Currencies are represented by 3 characters as defined in ISO 4217.

  • Dates & times are returned in UTC using ISO 8601 format with second accuracy. With requests, when no TZ is supplied, the configured TZ of the authenticated user is used, or Australia/Sydney if no TZ is configured.

  • Amounts are always in cents with no decimals unless otherwise stated.

  • Zepto provides static public IP addresses for all outbound traffic, including webhooks.

    • Sandbox IP:
    • Production IPs: and

System Status

Check the platform status, or subscribe to receive notifications at status.zeptopayments.com.
If you would like to check platform status programmatically, please refer to status.zeptopayments.com/api.

Breaking Changes

A breaking change is assumed to be:

  • Renaming a parameter (request/response)

  • Removing a parameter (request/response)

  • Changing a parameter type (request/response)

  • Renaming a header (request/response)

  • Removing a header (request/response)

  • Application of stricter validation rules for request parameters

  • Reducing the set of possible enumeration values for a request

  • Changing a HTTP response status code

We take backwards compatibility very seriously, and will make every effort to ensure this never changes. In the unfortunate (and rare) case where a breaking change can not be avoided, these will be announced well in
advance, enabling a transition period for API consumers.

The following are not assumed to be a breaking change and must be taken into account by API consumers:

  • Addition of optional new parameters in request

  • Addition of new parameters in response

  • Addition of new headers in request

  • Reordering of parameters in response

  • Softening of validation rules for request parameters

  • Increasing the set of possible enumeration values

In the case of non breaking changes, a transition period may not be provided, meaning the possibility of such changes occurring must be considered in consumers' logic so as not to break any integrations with both API and Webhooks.


Try it out

The best way to familiarise yourself with our API is by interacting with it.

We've preloaded a collection with all our endpoints for you to use in Postman.
Before you start, import a copy of our API collection:

Run in Postman

Okay, let's get things setup!

  1. Create a Zepto account

    If you haven't already, you'll want to create a sandbox Zepto account at https://go.sandbox.zeptopayments.com

  2. Register your application with Zepto

    Sign in and create an OAuth2 application: https://go.sandbox.zeptopayments.com/oauth/applications.

    Zepto OAuth2 app create

  3. Generate personal access tokens

    The quickest way to access your Zepto account via the API is using personal access tokens. Click on your newly created application from your [application list](https://go.sandbox.zeptopayments.com/oauth applications) and click on

    + Personal Access Token.

    [![Zepto locate personal OAuth2 tokens](https://raw.githubusercontent.com/zeptofs/public_assets/master/images/split_personal_access_tokens_empty.png)](https://raw.githubusercontent.com/zeptofs/public_assets/master/images/split_personal_access_tokens_empty.png)
    _(You'll have the option to give the token a title)_
    [![Zepto personal OAuth2 tokens](https://raw.githubusercontent.com/zeptofs/public_assets/master/images/split_personal_access_token.png)](https://raw.githubusercontent.com/zeptofs/public_assets/master/images/split_personal_access_token.png)
    > **_NOTE:_** Please note that personal access tokens do not expire.
  4. Use personal access token in Postman

    You can use this access_token to authorise any requests to the Zepto API in Postman by choosing the Bearer Token option under the Authorization tab.

    Postman use personal OAuth2 tokens

  5. Make an API request!

    You are now ready to interact with your Zepto account via the API! Go ahead and send a request using Postman.

    Postman use personal OAuth2 tokens

Get started

This guide will help you setup an OAuth2 app in order to get authenticated & authorised to communicate with the Zepto API.

Before you start:

  • We use the term user below but the user can be a third party or the same user that owns the OAuth2 application.

  • As noted below, some access tokens expire every 2 hours. To get a new access token use the refresh grant strategy to swap a refresh token for a new access token.

  1. Create a Zepto account

    If you haven't already, you'll want to create a sandbox Zepto account at https://go.sandbox.zeptopayments.com.

  2. Choose authentication method

    All requests to the Zepto API require an access_token for authentication. There are two options for obtaining these tokens, the correct option will depend on your use case:

    Personal access token If you only need to access your own Zepto account via the API, then using personal access tokens are the most straight-forward way. Refer to Personal access token to setup. These tokens do not expire so no refreshing is required.

    OAuth grant flow When you require your application to act on behalf of other Zepto accounts you'll need to implement the OAuth grant flow process. Refer to OAuth grant flow guide to setup. There is also an OAuth grant flow tutorial. These access tokens expire every 2 hours, unless the offline_access scope is used in which case the tokens will not expire.

Personal access token

If you're looking to only access your own account via the API, you can generate a personal access token from the UI. These tokens do not expire, but can be deleted.

  • To do this, sign in to your Zepto account and create an application if you haven't already. Click on your application from your application list and click on Personal access.

    Zepto locate personal OAuth2 tokens

    (You'll have the option to give the token a title)

    Zepto personal OAuth2 tokens

  • Now that you have an access_token you can interact with your Zepto account via the API.

    To do so, you must simply append the access token to the header of any API request: Authorization: Bearer {access_token}

OAuth grant flow

  1. Register your application with Zepto

    Once you've got your account up and running, sign in and create an OAuth2 profile for your application: https://go.sandbox.zeptopayments.com/oauth/applications

    NameThe name of your application. When using the Authorisation Grant Flow, users will see this name as the application requesting access to their account.
    Redirect URISet this to your application's endpoint charged with receiving the authorisation code.
  2. Obtain an authorisation code

    Construct the initial URL the user will need to visit in order to grant your application permission to act on his/her behalf. The constructed URL describes the level of permission (scope), the application requesting permission (client_id) and where the user gets redirected once they've granted permission (redirect_uri).

    The URL should be formatted to look like this:

    response_typeAlways set to code
    client_idThis is your Application ID as generated when you registered your application with Zepto
    redirect_uriURL where the user will get redirected along with the newly generated authorisation code
    scopeThe scope of permission you're requesting
  3. Exchange the authorisation code for an access token

    When the user visits the above-mentioned URL, they will be presented with a Zepto login screen and then an authorisation screen:

    Authorise OAuth2 app

    After the user has authorised your application, they will be returned to your application at the URL specified in redirect_uri along with the code query parameter as the authorisation code.

    Finally, the authorisation code can then be exchanged for an access token and refresh token pair by POSTing to: https://go.sandbox.zeptopayments.com/oauth/token

    Note The authorisation code is a ONE-TIME use code. It will not work again if you try to POST it a second time.

    grant_typeSet to authorization_code
    client_idThis is your Application ID as generated when you registered your application with Zepto
    client_secretThis is your Secret as generated when you registered your application with Zepto
    codeThe authorisation code returned with the user (ONE-TIME use)
    redirect_uriSame URL used in step 3
  4. Wrap-up

    Now that you have an access token and refresh token, you can interact with the Zepto API as the user related to the access token.
    To do so, you must simply append the access token to the header of any API request: Authorization: Bearer {access_token}

OAuth grant flow tutorial

The OAuth grant flow process is demonstrated using Postman in the steps below.

Before you start, load up our API collection:

Run in Postman

A screencast of this process is also available:

  1. Create a Zepto account

    If you haven't already, you'll want to create a sandbox Zepto account at https://go.sandbox.zeptopayments.com

  2. Register your application with Zepto

    Sign in and create an OAuth2 application: https://go.sandbox.zeptopayments.com/oauth/applications.

    Use the special Postman callback URL: https://www.getpostman.com/oauth2/callback

    Zepto OAuth2 app setup

  3. In Postman, setup your environment variables

    We've included the Zepto Public Sandbox environment to get you started. Select it in the top right corner of the window then click the Postman Quick-Look icon icon and click edit.

    Edit Postman environment

    Using the details from the OAuth2 app you created earlier, fill in the oauth2_application_id & oauth2_secret fields.

    Fill in environment values

  4. Setup the authorization

    Click on the Authorization tab and select OAuth 2.0

    Postman Authorization tab

    Click the Get New Access Token button

    Postman get new access token

    Fill in the OAuth2 form as below:

    Postman OAuth2

  5. Get authorised

    Click Request Token and wait a few seconds and a browser window will popup

    Sign in with your Zepto account (or any other Zepto account you want to authorise).

    Sign in Zepto to authorise via OAuth2

    Click Authorise to allow the app to access the signed in account. Once complete, Postman will automatically exchange the authorisation code it received from Zepto for the access_token/refresh_token pair. It will then store the access_token/refresh_token for you to use in subsequent API requests. The access_token effectively allows you to send requests via the API as the user who provided you authorisation.

    Authorise OAuth2 app

  6. You're now ready to use the API

    Select an endpoint from the Zepto collection from the left hand side menu. Before you send an API request ensure you select your access token and Postman will automatically add it to the request header.

    Postman use token

NOTE: Remember to select the access token everytime you try
a new endpoint. Have fun!

Authentication and Authorisation

Zepto uses OAuth2 over https to manage authentication and authorisation.

OAuth2 is a protocol that lets external applications request permission from another Zepto user to send requests on their behalf without getting their password.

This is preferred over Basic Authentication because access tokens can be limited by scope and can be revoked by the user at any time.

New to OAuth2? DigitalOcean has a fantastic 5 minute introduction to OAuth2.

We currently support the authorisation code and refresh token grants.

Authorisation Code Grant

This type of grant allows your application to act on behalf of a user. If you've ever used a website or application with your

Google, Twitter or Facebook account, this is the grant being used.

See the Get Started guide for step by step details on how to use this grant.

Refresh Token Grant

Code sample

curl -F "grant_type=refresh_token" \
      -F "client_id={{oauth2_application_id}}" \
      -F "client_secret={{oauth2_application_secret }}" \
      -F "refresh_token={{refresh_token}}" \
      -X POST https://go.sandbox.zeptopayments.com/oauth/token

Example response

    "access_token": "ad0b5847cb7d254f1e2ff1910275fe9dcb95345c9d54502d156fe35a37b93e80",
    "token_type": "bearer",
    "expires_in": 7200,
    "refresh_token": "cc38f78a5b8abe8ee81cdf25b1ca74c3fa10c3da2309de5ac37fde00cbcf2815",
    "scope": "public"

When using the authorisation code grant above, Zepto will return a refresh token along with the access token. Access tokens are short lived and last 2 hours but refresh tokens do not expire.

When the access token expires, instead of sending the user back through the authorisation flow you can use the refresh token to retrieve a new access token with the same permissions as the old one.

The refresh_token gets regenerated and sent alongside the new access_token. In other words, refresh_tokens are single use so you'll want to store the newly generated refresh_token everytime you use it to get a new access_token

Making payments

In order to payout funds, you'll be looking to use the Payments endpoint. Whether you're paying out another Zepto account holder or anyone, the process is the same:

  1. Add the recipient to your Contact list.

  2. Make a Payment to your Contact.

Common use cases:

  • Automated payout disbursement (Referal programs, net/commission payouts, etc...)

  • Wage payments

  • Gig economy payments

  • Lending

Getting paid

POSTing a Payment Request

Provides the ability to send a Payment Request (get paid) to any Contact that has an accepted Agreement in place.

To send a Payment Request to a Contact using the API, you must first have an accepted Agreement with them.

To do so, you can send them an Open Agreement link or Unassigned Agreement link for them to Select & verify their bank account and accept the Agreement.

Having this in place will allow any future Payment Requests to be automatically approved and processed as per the Agreement terms.

Common use cases:

  • Subscriptions

  • On-account balance payments

  • Bill smoothing

  • Repayment plans

Example flow embedding an Open Agreement link using an iFrame in order to automate future Payment Request approvals:

Hosted Open Agreement

Idempotent requests

Example response

  "errors": [
      "title": "Duplicate idempotency key",
      "detail": "A resource has already been created with this idempotency key",
      "links": {
        "about": "https://docs.zepto.money/"
      "meta": {
        "resource_ref": "PB.1a4"

The Zepto API supports idempotency for safely retrying requests without accidentally performing the same operation twice.

For example, if a Payment is POSTed and a there is a network connection error, you can retry the Payment with the same idempotency key to guarantee that only a single Payment is created. In case an idempotency key is not supplied and a Payment is retried, we would treat this as two different payments being made.

To perform an idempotent request, provide an additional Idempotency-Key:<key> header to the request.

You can pass any value as the key but we suggest that you use [V4 UUIDs](https://www.uuidtools.com/generate v4) or another appropriately random string.

Keys expire after 24 hours. If there is a subsequent request with the same idempotency key within the 24 hour period, we will return a 409 Conflict.

  • The meta.resource_ref value is the reference of the resource that was previously created with the conflicting idempotency key.

  • The Idempotency-Key header is optional but recommended.

  • Only the POST action for the Payments, Payment Requests and Refunds endpoints support the use of the Idempotency-Key.

  • Endpoints that use the GET or DELETE actions are idempotent by nature.

  • A request that quickly follows another with the same idempotency key may return with 503 Service Unavailable. If so, retry the request after the number of seconds specified in the Retry-After response header.

Caution: Keys are scoped to the User making the request. This means if User A makes a request with an access token they have generated to an Account using idempotency key 123 and User B makes a request with their own access token to the same Account using idempotency key 123 the requests will be treated as different and not idempotent.

Currently the following POST requests can be made idempotent. We strongly recommend sending a unique Idempotency-Key header when making those requests to allow for safe retries:

Error responses

Example detailed error response

  "errors": [
      "title": "A Specific Error",
      "detail": "Details about the error",
      "links": {
        "about": "https://docs.zepto.money/..."

Example resource error response

  "errors": "A sentence explaining error/s encounted"

The Zepto API returns two different types of error responses depending on the context.

Detailed error responses are returned for:

  • Authentication

  • Request types

  • Idempotency

All other errors relating to Zepto specific resources(e.g. Contacts) will return the Resource error response style.

403 errors

403 errors are generally returned from any of our endpoints if your application does not have the required authorisation. This is usually due to:

Speeding up onboarding

Consider the following scenario:

Zepto is integrated in your application to handle payments.

A customer would like to use Zepto but does not yet have Zepto account.

You already have some information about this customer.

Given the above, in a standard implementation where a customer enables/uses Zepto within your application, these are the steps they would follow:

  1. Click on some sort of button within your app to use Zepto.

  2. They get redirected to the Zepto sign in page (possibly via a popup or modal).

  3. Since they don't yet have a Zepto account, they would click on sign up.

  4. They would fill in all their signup details and submit.

  5. They would be presented with the authorisation page.

  6. They would click the "Authorise" button and be redirected to your app.

Whilst not too bad, we can do better!

In order to speed up the process, we allow query string params to be appended to the authorisation URL. For instance, if we already have some information about the customer and know they probably don't have a Zepto account, we can embed this information in the authorisation URL.

Supported query string parameters

landingAccepted value: sign_up. What page the user should see first if not already signed in Default is the sign in page.Deprecated values: business_sign_up, personal_sign_up.
nicknameOnly letters, numbers, dashes and underscores are permitted.
This will be used to identify the account in Zepto.
nameBusiness account only. Business name.
abnBusiness account only. Business ABN.
phoneBusiness account only. Business phone number.
stateSee the sign up page for accepted values

All values should be URL encoded.

As an example, the following authorisation URL would display the personal sign up & prefill the first name field with George:


You can also pass the values directly to the sign up page outside of the OAuth2 authorisation process. Click on the following link to see the values preloaded:

Sandbox Testing Details

Example failure object

  "failure": {
      "code": "E302",
      "title": "BSB Not NPP Enabled",
      "detail": "The target BSB is not NPP enabled. Please try another channel."

Try out your happy paths and not-so happy paths; the sandbox is a great place to get started without transferring actual funds. All transactions are simulated and no communication with financial institutions is performed.

The sandbox works on a 1 minute cycle to better illustrate how transactions are received and the lifecyle they go through. In other words, every minute, we simulate communicating with financial institutions and update statuses and events accordingly.

All 6 digits BSBs are valid in the sandbox with the exception of 100000. This BSB allows you to simulate the adding of an invalid BSB. In production, only real BSBs are accepted.

Failed transactions will contain the following information inside the event:

  • Failure Code

  • Failure Title

  • Failure Details

DE Transaction failures

Using failure codes

To simulate a transaction failure, create a Payment or Payment Request with an amount corresponding to the desired failure code.

For example:

  • DE amount $1.05 will cause the credit transaction to fail, triggering the credit failure code E105 (Account Not Found).

  • DE amount $2.03 will cause the debit transaction to fail, triggering the debit failure code E203 (Account Closed).

Example scenarios

  1. Pay a contact with an invalid account number:
* Initiate a Payment for `$1.05`.
* Zepto will mimic a successful debit from your bank account.
* Zepto will mimic a failure to credit the contact's bank account.
* Zepto will automatically create a `payout_reversal` credit transaction back to your bank account.
  1. Request payment from a contact with a closed bank account:
* Initiate a Payment Request for `$2.03`.
* Zepto will mimic a failure to debit the contact's bank account.

NPP Payment failures

Using failure codes

To simulate a transaction failure, create a Payment with an amount corresponding to the desired failure code.

For example:

  • NPP amount $3.02 will cause the transaction to fail, triggering credit failure code E302 (BSB Not NPP Enabled).

  • NPP amount $3.04 will cause the transaction to fail, triggering credit failure code E304 (Account Not Found).

You will receive all the same notifications as if this happened in our live environment. We recommend you check out our article on what happens when an NPP Payment fails to learn more about what happens when an NPP Payment is unable to process.

Instant account verification accounts

When using any of our hosted solutions (Payment Requests,
Open Agreements or
Unassigned Agreements) you may want to test the Instant Account Verification (IAV) process where we accept online banking credentials to validate bank account access. To do so, you can use the following credentials:


NOTE: The credentials will work with any of the available financial institutions.



Scopes define the level of access granted via the OAuth2 authorisation process. As a best practice, only use the scopes your application will require.

publicView user's public information
agreementsManage user's Agreements
bank_accountsManage user's Bank Accounts
bank_connectionsManage user's Bank Connections
contactsManage user's Contacts
open_agreementsManage user's Open Agreements
paymentsManage user's Payments
payment_requestsManage user's Payment Requests
refundsManage user's Refunds
transfersManage user's Transfers
transactionsAccess user's Transactions
webhooksManage user's Webhook events
offline_accessCreate non-expiring access tokens for user

NOTE: Please use offline_access with discretion, as you'll have no direct way to invalidate the token. Please contact Zepto immediately if any token may have potentially been compromised.


Example response headers

Link: <http://api.sandbox.zeptopayments.com/contacts?page=2>; rel="next"

Per-Page: 25

Pagination information can be located in the response headers: Link & Per-Page

All collection endpoints are paginated to Per-Page: 25 by default. (100 per page is max, any value above will revert to 100)

You can control the pagination by including per_page=x and/or page=x in the endpoint URL params.

The Link header will be optionally present if a "next page" is available to navigate to. The next page link is identified with rel="next"

Legacy Pagination: Some existing users may still be on a transitional legacy version of pagination.

The Legacy version returns some extra deprecated header values: Total plus rel="last" & rel="prev" in Link.

Please transition to only using the rel="next" from the Link header, as all other values are deprecated.


Example request

  "...": "...",
  "metadata": {
    "remitter": "CustomRem"

You can elect to assign a remitter name on a per-request basis when submitting Payments & Payment Requests. Simply append the remitter key and a value within the metadata key.

  • For Payments, the party being credited will see the designated remitter name along the entry on their bank statement.

  • For Payment Requests, the party being debited will see the designated remitter name along the entry on their bank statement.

NOTE: The remitter name MUST be between 3 and 16 characters.


Zepto will automatically aggregate debits that are:

  • From the same bank account; and

  • Have the same description; and

  • Initiated by the same Zepto account.

Likewise for credits:

  • To the same bank account; and

  • Have the same description; and

  • Initiated by the same Zepto account.

Should you prefer debit aggregation to be disabled, please contact [email protected]. Note that additional charges may apply.


Example response

  "event": {
    "type": "object.action",
    "at": "yyyy-mm-ddThh:mm:ssZ",
    "who": {
      "account_id": "x",
      "bank_account_id": "x"
  "data": [

Please refer to our help centre article on webhooks for more information and an overview of what you can achieve with webhooks.

We support two main categories of webhooks:

  1. Owner: These webhooks are managed by the owner of the Zepto account and only report on events owned by the Zepto account.

  2. App: These webhooks are managed by the Zepto OAuth2 application owner and will report on events relating to any authorised Zepto account (limited by scope).

eventobjecttrueWebhook event details
» typestringtrueThe webhook event key (list available in the webhook settings)
» atstring(date-time)trueWhen the event occurred
» whoobjecttrueWho the webhook event relates to
»» account_idstring(uuid)trueThe Zepto account who's the owner of the event
»» bank_account_idstring(uuid)trueThe above Zepto account's bank account
data[object]trueArray of response bodies

Data schemas

Use the following table to discover what type of response schema to expect for for the data.[{}] component of the webhook delivery.

Our Delivery Promises

  1. We only consider a webhook event delivery as failed if we don't receive any http response code (2xx, 4xx, 5xx, etc.)

  2. We will auto-retry failed deliveries every 5 minutes for 1 hour.

  3. Delivery order for webhook events is not guaranteed.

  4. We guarantee at least 1 delivery attempt.

For redelivery of webhooks, check out our Webhook/WebhookDelivery API endpoints.

NOTE: In the sandbox environment, webhook deliveries will only be retried once, to allow for easier testing of failure scenarios.

Request ID

Example header

Split-Request-ID: 07f4e8c1-846b-5ec0-8a25-24c3bc5582b5

Zepto provides a Split-Request-ID header in the form of a UUID which uniquely identifies a webhook event. If the webhook event is retried/retransmitted by Zepto, the UUID will remain the same. This allows you to check if a webhook event has been previously handled/processed.

Checking Webhook Signatures

Example header


Zepto signs the webhook events it sends to your endpoints. We do so by including a signature in each event’s Split-Signature header. This allows you to validate that the events were indeed sent by Zepto.

Before you can verify signatures, you need to retrieve your endpoint’s secret from your Webhooks settings. Each endpoint has its own unique secret; if you use multiple endpoints, you must obtain a secret for each one.

The Split-Signature header contains a timestamp and one or more signatures. All separated by . (dot).

Example code

# Shell example is not available

package main

import (

func main() {
    secret := "1234"
    message := "full payload of the request"
    splitSignature := "1514772000.f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f"

    data := strings.Split(splitSignature, ".")
    timestamp, givenSignature := data[0], data[1]

    signedPayload := timestamp + "." + message

    hash := hmac.New(sha256.New, []byte(secret))
    expectedSignature := hex.EncodeToString(hash.Sum(nil))

    // f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f
    // f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f

import hashlib

import hmac

split_signature = '1514772000.f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f'

secret = bytes('1234').encode('utf-8')

message = bytes('full payload of the request').encode('utf-8')

data = split_signature.split('.')

timestamp = data[0]

given_signature = data[1]

signed_payload = timestamp + '.' + message

expected_signature = hmac.new(secret, signed_payload,


# > f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f


# > f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f

require 'openssl'

split_signature =

secret = '1234'

message = 'full payload of the request'

timestamp, given_signature, *other = split_signature.split('.')

signed_payload = timestamp + '.' + message

expected_signature = OpenSSL::HMAC.hexdigest('sha256', secret,


# => f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f


# => f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f

var crypto = require('crypto')

var message = 'full payload of the request'

var secret = '1234'

var splitSignature =

var data = splitSignature.split('.')

var timestamp = data[0]

var givenSignature = data[1]

var signedPayload = timestamp + '.' + message

var expectedSignature = crypto.createHmac('sha256',


// f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f


// f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f

$split_signature = '1514772000.f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f';

$secret = '1234';

$message = 'full payload of the request';

list($timestamp, $given_signature, $other) = explode('.', $split_signature);

$signed_payload = $timestamp . "." . $message;

$expected_signature = hash_hmac('sha256', $signed_payload, $secret, false);

echo $expected_signature;
// f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f

echo "\n";

echo $given_signature;
// f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f

import javax.crypto.Mac;

import javax.crypto.spec.SecretKeySpec;

class Main {
  public static void main(String[] args) {
    try {
      String splitSignature = "1514772000.f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f";
      String secret = "1234";
      String message = "full payload of the request";

      String[] data = splitSignature.split("\\.");
      String timestamp = data[0];
      String givenSignature = data[1];

      String signedPayload = timestamp + "." + message;

      Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
      SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");

      String expectedSignature = javax.xml.bind.DatatypeConverter.printHexBinary(sha256_HMAC.doFinal(signedPayload.getBytes())).toLowerCase();

      // f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f

      // f04cb05adb985b29d84616fbf3868e8e58403ff819cdc47ad8fc47e6acbce29f
    catch (Exception e){

Step 1. Extract the timestamp and signatures from the header

Split the header, using the . (dot) character as the separator, to get a list of elements.

timestampUnix time in
seconds when the signature was created
signatureRequest signature
otherPlaceholder for future parameters (currently not used)

Step 2: Prepare the signed_payload string

You achieve this by concatenating:

  • The timestamp from the header (as a string)

  • The character . (dot)

  • The actual JSON payload (request body)

Step 3: Determine the expected signature

Compute an HMAC with the SHA256 hash function. Use the endpoint’s signing secret as the key, and use the signed_payload string as the message.

Step 4: Compare signatures

Compare the signature in the header to the expected signature. If a signature matches, compute the difference between the current timestamp and the received timestamp, then decide if the difference is within your tolerance.

To protect against timing attacks, use a constant-time string comparison to compare the expected signature to each of the received signatures.

NOTE: The sandbox environment allow both HTTP and HTTPS webhook URLs. The live environment however will only POST to HTTPS URLs.

Looking for more? Our docs are open sourced! https://github.com/zeptofs/api-documentation